window

package module
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package window draws refract charts in a native window.

It is the fourth surface, after the two vector emitters and the browser canvas: a chart on a desktop, panned and zoomed with a mouse rather than exported and opened. The window itself comes from gogpu — windowing, input and lifecycle across Windows, macOS, Linux/X11 and Linux/Wayland, with no cgo — and the chart is rasterized by the same CPU backend that writes a PNG and presented as one texture.

w := window.New(window.Title("Signal"), window.Size(900, 600))
live, _ := p.Live(w.Target())
defer live.Close()
in := live.Input()

err := w.Run(window.Handler{
    Frame:   live.Draw,
    Move:    func(x, y float64) { in.Move(x, y) },
    Press:   func(x, y float64) { in.Down(x, y) },
    Release: func(x, y float64) { in.Up(x, y) },
    Scroll:  func(x, y, d float64) { in.Wheel(x, y, d) },
    Resize:  func(w, h int) { in.Resize(w, h) },
})

Package show is that same wiring in one call, and is what most callers want.

Why the chart is rasterized on the CPU

Because there is one implementation of every mark, and a window should show what a file would. The GPU is not idle in this arrangement — it composites and presents — and gg's own GPU tier can be switched on underneath, without anything here changing, by importing github.com/timzifer/refract/backend/gg/gpu.

The cost of the arrangement is one texture upload per changed frame. It is paid only when the frame changed: refract repaints nothing when a frame is identical to the last, the rasterizer stamps its buffer with a generation that says whether it did, and the window compares two integers rather than two buffers. Between that and an event-driven loop that blocks on the operating system's queue, a chart nobody is touching costs no CPU at all.

A separate module

Like backend/gg, this is a nested module: importing refract's core still yields a dependency graph with nothing in it but the standard library, and a program that renders SVG on a server links no window layer. It depends on backend/gg as well as on the core, because the rasterizer is where the marks are drawn.

Status

The window layer is young, and so is the GPU stack under it. A chart in a window is v0.6's newest surface and the least proven one; the vector emitters remain the supported path for output that has to be right.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// Frame is called before each present, and is where the chart is drawn.
	// Returning an error stops the loop and is what [Window.Run] returns.
	Frame func() error

	// Move, Press and Release report the pointer. Press and Release are the
	// primary button; a window reports no other, because a chart has no use
	// for one yet.
	Move    func(x, y float64)
	Press   func(x, y float64)
	Release func(x, y float64)

	// DoubleClick reports two presses in the same place in quick succession,
	// which every interactive chart uses to reset the view.
	DoubleClick func(x, y float64)

	// Scroll reports the wheel, in the browser's pixel convention: positive
	// scrolls the content away from the reader. Lines and pages are converted
	// to it, so a handler sees one unit whatever the platform counts in.
	Scroll func(x, y, delta float64)

	// Resize reports the window's new content size in device-independent
	// pixels, and Rescale a change of device pixel ratio — dragging a window
	// onto a display with a different one.
	Resize  func(w, h int)
	Rescale func(dpr float64)

	// Closed is called once, as the window is going away.
	Closed func()
}

Handler is what a window reports. Every field is optional, and every callback runs on the goroutine that called Window.Run, one at a time — so a handler may touch a chart without a lock.

Positions are in device-independent pixels relative to the window's content area, which is the same space the chart is laid out in: a point handed to a handler can go straight to Live.Move.

type Option

type Option func(*config)

Option configures a window.

func FollowSize

func FollowSize(on bool) Option

FollowSize controls whether the chart is redrawn at the window's size when the window is resized. It is on by default; turn it off to keep the chart at the size it was opened with and let the window letterbox it.

func Font

func Font(opts ...ggbackend.Option) Option

Font supplies the fonts the chart is drawn with, exactly as the file targets take them. See github.com/timzifer/refract/backend/gg.WithFont.

func Size

func Size(w, h int) Option

Size sets the window's initial size in device-independent pixels. It defaults to the chart's own size, which is usually what is wanted: a window that opens at the size the plot was designed for.

func Title

func Title(s string) Option

Title sets the window's title bar text.

type Window

type Window struct {
	// contains filtered or unexported fields
}

Window is a native window a chart is drawn in.

It is two things joined at one seam. Window.Target is a drawing surface — the same CPU rasterizer that writes a PNG, drawing into memory — and Window.Run is an event loop that presents that memory as a texture and reports what the reader does to it. Neither knows what a chart is; the package next door, backend/window/show, is what joins them to a plot.

A Window is used from one goroutine: the one that calls Run, which is the goroutine every callback then runs on.

func New

func New(opts ...Option) *Window

New returns a window that has not been opened yet.

Nothing is created until Window.Run: a window is opened by running it, and a program that builds one and never runs it has not taken a screen.

func (*Window) Close

func (w *Window) Close()

Close asks the window to close, ending Window.Run.

func (*Window) Redraw

func (w *Window) Redraw()

Redraw asks for a frame. It is what to call after changing something the window is showing; the loop is event-driven and idle otherwise, which is why a change nobody announces is a change nobody sees.

func (*Window) Run

func (w *Window) Run(h Handler) error

Run opens the window and runs its event loop until the window is closed.

The loop is event-driven: it blocks on the operating system's event queue and wakes for input, a resize, or a Window.Redraw. A chart that nobody is touching costs nothing, which is the property that makes a window worth having over a browser tab and is why Frame is a callback rather than a polling loop.

Run returns the first error a Frame reported, or the error the window layer failed with. It must be called on the main goroutine on the platforms that insist on it — macOS does — which in Go means from main, before anything else takes over.

func (*Window) ScaleFactor

func (w *Window) ScaleFactor() float64

ScaleFactor reports the display's device pixel ratio.

func (*Window) Size

func (w *Window) Size() (int, int)

Size reports the window's current size in device-independent pixels, which is the size the chart should be laid out at.

func (*Window) Target

func (w *Window) Target() ir.Target

Target returns the surface the chart is drawn into.

It is an ordinary ir.Target: hand it to Plot.Live and draw whenever there is something new to show. What the window adds is that the pixels end up on a screen.

Directories

Path Synopsis
cmd
demo command
Command demo opens a chart in a native window.
Command demo opens a chart in a native window.
Package show opens a plot in a native window.
Package show opens a plot in a native window.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL