Documentation
¶
Overview ¶
Package painter is a prototype of the "Painter" abstraction that lets a single widget's Draw method target three deployment families:
- WUI (browser wasm + <canvas> + putImageData) → PixelPainter
- GUI (native window; SDL2, Ebitengine, image/png export…) → PixelPainter
- TUI (terminal cell grid + ANSI escape codes) → CellPainter
The current go-widgets/toolkit widgets are hard-bound to a []byte + surfaceW pair — great for pixel back-ends, incompatible with a cell grid. This repo prototypes a redesign around a primitive-set interface:
type Painter interface {
FillRect(r Rect, c RGBA)
StrokeRect(r Rect, c RGBA, lineW int)
Text(x, y int, s string, ink RGBA)
PutPixel(x, y int, c RGBA)
}
A widget's Draw becomes:
func (b *Button) Draw(p Painter, theme *Theme) {
r := b.Bounds
p.FillRect(r, theme.Surface)
p.StrokeRect(r, theme.Border, 1)
p.Text(r.X+8, r.Y+8, b.Label, theme.OnSurface)
}
The same widget code renders identically in a browser canvas (PixelPainter), a native window (PixelPainter again — the host consumes the buffer differently), a terminal (CellPainter maps RGBA to ANSI 16-colour, snaps rects to cells + uses box-draw glyphs for strokes), or an SVG snapshot (not shipped in this prototype — see go-widgets/svg).
Status: PROTOTYPE. The API surface is deliberately small (5 primitives). Once validated the full toolkit widget set migrates + the prototype folds into go-widgets/toolkit as its v1.0 rendering path.
Index ¶
- Constants
- type Button
- type Cell
- type CellPainter
- func (p *CellPainter) DrawImage(dst Rect, src []byte, srcW, srcH int)
- func (p *CellPainter) DrawMask(dst Rect, mask []byte, stride int, ink RGBA)
- func (p *CellPainter) FillRect(r Rect, c RGBA)
- func (p *CellPainter) FillRoundRect(r Rect, radius int, c RGBA)
- func (p *CellPainter) PopClip()
- func (p *CellPainter) PopTranslate()
- func (p *CellPainter) PushClip(r Rect)
- func (p *CellPainter) PushTranslate(dx, dy int)
- func (p *CellPainter) PutPixel(x, y int, c RGBA)
- func (p *CellPainter) Size() (int, int)
- func (p *CellPainter) StrokeRect(r Rect, c RGBA, lineW int)
- func (p *CellPainter) StrokeRoundRect(r Rect, radius int, c RGBA, lineW int)
- func (p *CellPainter) Text(x, y int, s string, ink RGBA)
- func (p *CellPainter) WriteANSI(w io.Writer) (int, error)
- type Clipper
- type Face
- type FacePainter
- type FillRule
- type ImagePainter
- type Label
- type MaskPainter
- type Painter
- type Path
- type PathPainter
- type PixelPainter
- func (p *PixelPainter) DrawImage(dst Rect, src []byte, srcW, srcH int)
- func (p *PixelPainter) DrawMask(dst Rect, mask []byte, stride int, ink RGBA)
- func (p *PixelPainter) FillPath(pth *Path, c RGBA, rule FillRule)
- func (p *PixelPainter) FillRect(r Rect, c RGBA)
- func (p *PixelPainter) FillRoundRect(r Rect, radius int, c RGBA)
- func (p *PixelPainter) PopClip()
- func (p *PixelPainter) PopTranslate()
- func (p *PixelPainter) PushClip(r Rect)
- func (p *PixelPainter) PushTranslate(dx, dy int)
- func (p *PixelPainter) PutPixel(x, y int, c RGBA)
- func (p *PixelPainter) Size() (int, int)
- func (p *PixelPainter) StrokePath(pth *Path, c RGBA, width float64)
- func (p *PixelPainter) StrokeRect(r Rect, c RGBA, lineW int)
- func (p *PixelPainter) StrokeRoundRect(r Rect, radius int, c RGBA, lineW int)
- func (p *PixelPainter) Text(x, y int, s string, ink RGBA)
- type ProgressBar
- type RGBA
- type Rect
- type Theme
- type Translator
- type Widget
Constants ¶
const ( // NonZero fills a point when the signed edge-crossing count around it is // non-zero — the intuitive rule for most icon shapes. NonZero = vector.NonZero // EvenOdd fills a point when the crossing count is odd, so a shape drawn over // itself punches a hole. EvenOdd = vector.EvenOdd )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Button ¶
Button is the prototype's canonical widget — solid fill + border + centred label. Every real widget in the toolkit follows the same four-line pattern.
type Cell ¶
Cell is one terminal cell — a rune plus a foreground and background colour. Painters serialize cell grids into ANSI escape sequences at render time.
type CellPainter ¶
CellPainter maps the primitive set onto a fixed-size cell grid. Coordinates are in cells; a rectangle of size (10, 3) is 10 cells wide and 3 cells tall regardless of the terminal's font.
Colour: written to a 24-bit-ANSI-truecolor terminal at flush time. The primitive set carries full RGBA; the terminal handles quantiz- ation. This keeps the widget code identical between pixel + cell back-ends.
func NewCellPainter ¶
func NewCellPainter(w, h int) *CellPainter
NewCellPainter builds a fresh painter over an allocated grid. The grid is initialized to space + black on black — the widget draws its own background.
func (*CellPainter) DrawImage ¶ added in v0.5.0
func (p *CellPainter) DrawImage(dst Rect, src []byte, srcW, srcH int)
DrawImage maps the image onto the cell grid: each cell takes the colour of the source pixel it lands on, as a full-block glyph — which is what CellPainter.PutPixel already means by a pixel. Implements ImagePainter.
A terminal cannot show an image, so this is the same honest degradation PutPixel already makes — a coloured cell rather than nothing at all.
func (*CellPainter) DrawMask ¶ added in v0.9.0
func (p *CellPainter) DrawMask(dst Rect, mask []byte, stride int, ink RGBA)
DrawMask puts a coloured cell wherever the mask is more than half covered: a cell is either the glyph's colour or it is not, so the coverage has to resolve to a decision. Implements MaskPainter.
func (*CellPainter) FillRect ¶
func (p *CellPainter) FillRect(r Rect, c RGBA)
FillRect paints a solid block of cells. The rune stays ' '; only the background colour is set. StrokeRect overlays box characters on top.
func (*CellPainter) FillRoundRect ¶ added in v0.1.1
func (p *CellPainter) FillRoundRect(r Rect, radius int, c RGBA)
FillRoundRect can't round on a cell grid (a cell is atomic), so it falls back to a square FillRect -- the rounding is a no-op here.
func (*CellPainter) PopClip ¶ added in v0.1.3
func (p *CellPainter) PopClip()
PopClip removes the most recent PushClip. Implements Clipper.
func (*CellPainter) PopTranslate ¶ added in v0.4.0
func (p *CellPainter) PopTranslate()
PopTranslate removes the most recent PushTranslate. Implements Translator.
func (*CellPainter) PushClip ¶ added in v0.1.3
func (p *CellPainter) PushClip(r Rect)
PushClip confines subsequent writes to r (intersected with any enclosing clip). Implements Clipper.
func (*CellPainter) PushTranslate ¶ added in v0.4.0
func (p *CellPainter) PushTranslate(dx, dy int)
PushTranslate shifts subsequent drawing by dx,dy, on top of any enclosing translation. Implements Translator.
func (*CellPainter) PutPixel ¶
func (p *CellPainter) PutPixel(x, y int, c RGBA)
PutPixel paints a single cell as a filled block character. Useful for pixel-precise widgets that want to render "dots" on a terminal.
func (*CellPainter) Size ¶
func (p *CellPainter) Size() (int, int)
Size returns width × height in cells.
func (*CellPainter) StrokeRect ¶
func (p *CellPainter) StrokeRect(r Rect, c RGBA, lineW int)
StrokeRect draws a 1-cell-wide box using Unicode box-draw runes. lineW is ignored — a terminal cell is atomic.
func (*CellPainter) StrokeRoundRect ¶ added in v0.1.1
func (p *CellPainter) StrokeRoundRect(r Rect, radius int, c RGBA, lineW int)
StrokeRoundRect falls back to the square box-draw StrokeRect on a cell grid.
func (*CellPainter) Text ¶
func (p *CellPainter) Text(x, y int, s string, ink RGBA)
Text writes s as-is starting at (x, y) — one rune per cell. UTF-8 wide characters are not policed at this prototype stage; a produc- tion CellPainter would use golang.org/x/text/width.
func (*CellPainter) WriteANSI ¶
func (p *CellPainter) WriteANSI(w io.Writer) (int, error)
WriteANSI serializes the grid as a single ANSI-encoded string (24-bit truecolor). Each row is prefixed with `\x1b[<y+1>;1H` so it starts at column 1 of its own row regardless of terminal wrap / raw-mode CR handling — the previous `\n`-only terminator broke in raw-mode Terminal.app (ONLCR off): LF alone doesn't reset column, so rows 1..N would be written to wrapped positions and the frame would end up blank except for the last row.
type Clipper ¶ added in v0.1.3
type Clipper interface {
PushClip(r Rect)
PopClip()
}
Clipper is an optional Painter capability: while a clip rect is pushed, every drawing primitive is confined to it (intersected with any enclosing clip). It is the seam a scrollable/overflowing widget uses to keep a child inside its own bounds — the base Painter interface deliberately cannot draw-clip, so widgets that need it type-assert:
if c, ok := p.(painter.Clipper); ok {
c.PushClip(bounds)
defer c.PopClip()
}
child.Draw(p, theme)
Both PixelPainter and CellPainter implement Clipper; a back-end that cannot clip simply does not, and the assertion is skipped.
type Face ¶ added in v0.2.0
type Face interface {
// FontData returns the original TrueType/OpenType sfnt bytes of the face.
// A consumer embeds or subsets these to render real, selectable text. The
// slice is read-only; callers must not mutate it.
FontData() []byte
// SizePx is the face's em size in painter units (pixels) — the size the
// widget laid its text out at, which the consumer reproduces so the run
// occupies the same box.
SizePx() int
// Ascent is the baseline offset from the text's top edge, in painter units.
// The Text/TextFace convention places (x, y) at the run's TOP-LEFT corner,
// so a baseline-origin back-end (PDF, PostScript) drops the pen by Ascent.
Ascent() int
}
Face is a resolved font face a run of text is drawn with. The base Text primitive carries only a string + ink, so a non-pixel painter (a vector or recording back-end) renders it in the painter's OWN fallback font. When the text is actually set in a TrueType/OpenType face — a widget running under go-widgets/toolkit's NewTrueTypeFont, say — that fallback loses both the real glyph shapes and the real advances, so the run mis-sizes.
Face is the bridge: it exposes just enough of the face for a vector back-end to embed the true font and place the text at the true size, WITHOUT the painter package taking on a font-engine dependency (it stays stdlib-only). The font's own layout (per-glyph advances, kerning) is reproduced by the consumer re-reading these same sfnt bytes, so the vector text lines up with the on-screen raster the widget laid itself out against.
A Face is handed to a FacePainter (see below); a painter that cannot use one never sees it.
type FacePainter ¶ added in v0.2.0
type FacePainter interface {
// TextFace draws s in face with (x, y) the run's top-left corner and ink the
// fill colour. Coordinates are painter units (pixels). Text laid out in
// visual order is passed as-is; the back-end maps each rune through the
// face's own cmap.
TextFace(x, y int, s string, face Face, ink RGBA)
}
FacePainter is an optional Painter capability: a back-end that can render real text in a specific font face implements it. It is the shaped-text seam a proportional/TrueType font uses so a vector or recording painter emits genuine selectable text (a PDF text-show operator, an SVG <text>, …) in the true face rather than the painter's built-in fallback font.
A font that owns a Face type-asserts, exactly like Clipper:
if fp, ok := p.(painter.FacePainter); ok {
fp.TextFace(x, y, s, face, ink)
return
}
p.Text(x, y, s, ink) // fallback: painter's own font
PixelPainter deliberately does NOT implement FacePainter: a raster font scan-converts its own glyph coverage and blits through PutPixel, so it never needs the seam. Only non-pixel back-ends (which cannot rasterise glyph masks) consume it.
type FillRule ¶ added in v0.3.0
FillRule selects how a path's winding count decides which side of the outline is filled: NonZero (non-zero winding) or EvenOdd. It is an alias of vector.FillRule.
type ImagePainter ¶ added in v0.5.0
ImagePainter is an optional Painter capability: it puts a block of RGBA pixels on the surface in one call.
The base interface can draw rectangles, rounded rectangles, text and single pixels — and nothing that carries pixels of its own. Every widget showing an image therefore had to spell it out one pixel at a time: the toolkit's Image, Thumbnail, Wallpaper, Browser, ColorPicker and both font paths all loop over the destination calling PutPixel, which is an interface call per pixel — about 700,000 of them for a full 1000x700 window. Applications with their own framebuffer went further and bypassed the painter completely, reaching for the raw buffer, which is exactly what stops them being hosted by a back-end that hands out a Painter and nothing else.
DrawImage scales src (srcW x srcH, 4 bytes per pixel, RGBA) into dst by nearest-neighbour sampling — the same mapping the hand-written loops used, so output is unchanged — and honours the active clip and translation like every other primitive.
A src that is too short for srcW*srcH*4 is ignored rather than read past its end: the caller has a bug, and a painter is the wrong place to panic.
type MaskPainter ¶ added in v0.9.0
MaskPainter is an optional Painter capability: it paints ONE colour through an 8-bit coverage mask in a single call.
This is what a glyph is. A rasterised glyph is not an image — it carries no colour of its own, only how much of each pixel the outline covers — and text is the one thing every widget draws. Before this, a font back-end had to walk the mask calling PutPixel, which shifts, bounds-tests, clip-tests and blends one pixel at a time, and had to scale the coverage by the ink's alpha itself on every pixel.
mask holds one coverage byte per pixel, row-major with the given stride, and mask[0] is the pixel at dst's top-left. 0 leaves the surface untouched, 255 lays down ink at its own alpha, and everything between composites — which is exactly what makes a glyph edge smooth rather than jagged.
ImagePainter carries pixels that bring their own colour; this carries coverage for a colour the caller names. A back-end may implement either, both, or neither.
type Painter ¶
type Painter interface {
// FillRect paints a solid rectangle.
FillRect(r Rect, c RGBA)
// StrokeRect paints a 1-line-wide border around r (no fill).
// lineW is a hint; back-ends that can't do variable strokes
// (a cell grid, for instance) ignore it.
StrokeRect(r Rect, c RGBA, lineW int)
// FillRoundRect fills r with the corners rounded to the given
// radius (in painter units). radius is clamped to half the
// smaller side. Pixel back-ends anti-alias the corners; back-ends
// that can't round (a cell grid) fall back to a square FillRect.
FillRoundRect(r Rect, radius int, c RGBA)
// StrokeRoundRect paints a 1-unit rounded border around r. Like
// StrokeRect, lineW is a hint; non-rounding back-ends fall back to
// a square StrokeRect.
StrokeRoundRect(r Rect, radius int, c RGBA, lineW int)
// PutPixel paints a single pixel at (x, y). On a CellPainter
// this promotes to a filled cell.
PutPixel(x, y int, c RGBA)
// Text paints ink text starting at (x, y). Font metrics come
// from the painter's own bitmap (PixelPainter's 5×7 font) or
// the terminal's own font (CellPainter — 1 cell per rune).
Text(x, y int, s string, ink RGBA)
// Size returns the painter's canvas dimensions in painter units
// (pixels for PixelPainter, cells for CellPainter). A widget
// that wants to fill the whole surface reads this instead of
// hard-coding a size.
Size() (w, h int)
}
Painter is the primitive-set every back-end implements. A widget composes only these calls; the back-end decides how they land on the actual output.
Coordinates are in the painter's OWN unit — pixel for PixelPainter, cell for CellPainter. The widget doesn't need to know which; the host sets the widget's Bounds in the right units before Draw is called.
type Path ¶ added in v0.3.0
Path is a mutable 2-D outline built from move / line / quadratic / cubic / close commands. It is an alias of vector.Path, so the builder methods and any path a consumer holds are unchanged by the extraction.
type PathPainter ¶ added in v0.3.0
type PathPainter interface {
// FillPath fills pth with colour c under the given winding rule. Curves are
// flattened; corner and edge pixels get fractional coverage (anti-aliased),
// composited through the painter's own pixel write so the active clip is
// honoured. An empty path, or one enclosing no area, paints nothing.
FillPath(pth *Path, c RGBA, rule FillRule)
// StrokePath paints pth's outline with colour c, centred on the path and
// width units wide, with round joins and caps. A closed sub-path strokes its
// closing segment too. width <= 0, a nil/empty path, or an isolated point
// paint nothing.
StrokePath(pth *Path, c RGBA, width float64)
}
PathPainter is an optional Painter capability: a back-end that can rasterise arbitrary 2-D outlines implements it. Like Clipper and FacePainter it is type-asserted, so the base Painter interface stays a fixed-primitive set and a back-end that cannot rasterise paths (a cell grid) simply does not implement it:
if pp, ok := p.(painter.PathPainter); ok {
pp.FillPath(icon, ink, painter.NonZero)
} else {
// coarse fallback in the base primitives, or skip the vector part
}
PixelPainter implements PathPainter with the go-gfx/gfx/vector anti-aliased scanline coverage rasterizer, compositing the coverage through its own pixel write so the active clip is honoured. CellPainter and any FacePainter-only back-end deliberately do NOT: a terminal cell is atomic and cannot carry sub-cell vector coverage, so the capability is reported absent and the consumer falls back.
type PixelPainter ¶
type PixelPainter struct {
// Buf is the destination RGBA byte slice (4 bytes per pixel).
// The buffer is written in place; callers own its lifecycle.
Buf []byte
// Width is the stride in pixels — number of pixels per row.
// The buffer's actual byte-stride is Width*4.
Width int
// Height is the number of rows.
Height int
// contains filtered or unexported fields
}
PixelPainter writes the primitive set into an RGBA byte buffer — the deployment target for the WUI (browser canvas + putImageData) and GUI (native window that consumes a []byte) families. The buffer + stride mirror the toolkit's current Draw signature; a widget migrated to Painter renders identically to today's toolkit output.
func NewPixelPainter ¶
func NewPixelPainter(buf []byte, width, height int) *PixelPainter
NewPixelPainter builds a fresh painter over an already-allocated buffer. The buffer must be exactly `4*width*height` bytes; a mismatch is not policed here (the primitive calls just no-op on out-of-bounds writes).
func NewPixelPainterBGRA ¶ added in v0.12.0
func NewPixelPainterBGRA(buf []byte, width, height int) *PixelPainter
NewPixelPainterBGRA is NewPixelPainter for a buffer whose pixels are BLUE, green, red, alpha -- the order a screen capture, a video frame and several native surfaces arrive in.
It exists because the alternative is worse in every direction. A consumer drawing widgets over captured pixels can swap the CAPTURE, which is the largest thing in the frame and the one part that must not be copied twice; or it can hand every colour in the theme over pre-swapped, which is dozens of values and one forgotten one away from a wrong colour nobody traces back. Swapping here costs two byte stores per pixel WRITTEN, and a widget writes a small part of a frame.
Measured in go-xrkit/desk: its canvas holds BGRA because ScreenCaptureKit hands over BGRA and the frame is swapped once on the way to the window. Every overlay the toolkit drew into it -- the screen number, the gallery marks, the application tiles -- came out with red and blue exchanged, so the orange selection ring was blue on the glasses.
func (*PixelPainter) DrawImage ¶ added in v0.5.0
func (p *PixelPainter) DrawImage(dst Rect, src []byte, srcW, srcH int)
DrawImage blits src into dst. Implements ImagePainter.
The fast path is one row at a time. When the destination is the same width as the source and the row is fully opaque and unclipped, the row is copied wholesale; otherwise each pixel is composited through the same blend the rest of the painter uses, so translucent images look identical to the per-PutPixel version that came before.
func (*PixelPainter) DrawMask ¶ added in v0.9.0
func (p *PixelPainter) DrawMask(dst Rect, mask []byte, stride int, ink RGBA)
DrawMask paints ink through mask. Implements MaskPainter.
Where it may write is decided once, like every other primitive here, so the per-pixel work is a coverage lookup and a blend and nothing else.
func (*PixelPainter) FillPath ¶ added in v0.3.0
func (p *PixelPainter) FillPath(pth *Path, c RGBA, rule FillRule)
FillPath fills pth with c under rule. See PathPainter.FillPath.
func (*PixelPainter) FillRect ¶
func (p *PixelPainter) FillRect(r Rect, c RGBA)
FillRect fills r with c. Out-of-bounds bytes are dropped so a widget that ranges past the edge doesn't panic.
This is the primitive the toolkit leans on hardest -- every background, every button, every table row -- and it used to be a PutPixel per pixel, which is a shift, two bounds tests, a clip test and a blend each: 700,000 of them for a window-sized fill. Where a fill may write is a rectangle, decided once; and an opaque fill writes the SAME four bytes everywhere, so one row is built and the rest of the rectangle is that row copied. A translucent fill still composites pixel by pixel, because its result depends on what was underneath.
func (*PixelPainter) FillRoundRect ¶ added in v0.1.1
func (p *PixelPainter) FillRoundRect(r Rect, radius int, c RGBA)
FillRoundRect fills r with corners rounded to radius (in pixels), anti- aliasing the corner edge. radius is clamped to half the smaller side; a radius <= 0 degrades to a plain FillRect. Corner-edge pixels are plotted with fractional alpha, which PutPixel composites onto the destination -- so a rounded button/pill reads as a smooth macOS-style shape rather than a jagged one.
func (*PixelPainter) PopClip ¶ added in v0.1.3
func (p *PixelPainter) PopClip()
PopClip removes the most recent PushClip. Implements Clipper.
func (*PixelPainter) PopTranslate ¶ added in v0.4.0
func (p *PixelPainter) PopTranslate()
PopTranslate removes the most recent PushTranslate. Implements Translator.
func (*PixelPainter) PushClip ¶ added in v0.1.3
func (p *PixelPainter) PushClip(r Rect)
PushClip confines subsequent drawing to r (intersected with any enclosing clip). Implements Clipper.
func (*PixelPainter) PushTranslate ¶ added in v0.4.0
func (p *PixelPainter) PushTranslate(dx, dy int)
PushTranslate shifts subsequent drawing by dx,dy, on top of any enclosing translation. Implements Translator.
func (*PixelPainter) PutPixel ¶
func (p *PixelPainter) PutPixel(x, y int, c RGBA)
PutPixel writes one RGBA at (x, y). Out-of-bounds writes are silently dropped.
Semi-transparent colours are src-over composited onto the existing pixel, so a theme colour like WhiteSur's borders rgba(0,0,0,0.12) paints as a subtle 12%-black hairline instead of a harsh opaque line. The two common cases stay exact and allocation-free:
- A == 0xFF (the vast majority of widget paint) overwrites verbatim, so opaque rendering is byte-identical to before.
- A == 0 (fully transparent) is a no-op.
Compositing over an opaque destination yields an opaque result, so a surface stays fully opaque for the host compositor.
func (*PixelPainter) Size ¶
func (p *PixelPainter) Size() (int, int)
Size returns Width × Height in pixels.
func (*PixelPainter) StrokePath ¶ added in v0.3.0
func (p *PixelPainter) StrokePath(pth *Path, c RGBA, width float64)
StrokePath paints pth's outline with c, width units wide. See PathPainter.StrokePath. The stroke coverage (the union of a rectangle per segment and a round join/cap disk at every vertex) is computed by the vector rasterizer and composited once.
func (*PixelPainter) StrokeRect ¶
func (p *PixelPainter) StrokeRect(r Rect, c RGBA, lineW int)
StrokeRect draws a 1-line-wide border around r. lineW is currently ignored — the pixel back-end can't easily draw thick strokes without antialiasing, which is out of scope for this prototype.
func (*PixelPainter) StrokeRoundRect ¶ added in v0.1.1
func (p *PixelPainter) StrokeRoundRect(r Rect, radius int, c RGBA, lineW int)
StrokeRoundRect paints a 1-pixel rounded border around r. The straight runs are crisp 1-px lines; the four corners are an anti-aliased quarter-ring.
type ProgressBar ¶
ProgressBar visualises a 0.0..1.0 value as a filled ratio of its bounds. Values outside the range clamp.
func (*ProgressBar) Draw ¶
func (b *ProgressBar) Draw(p Painter, theme *Theme)
Draw paints the empty track + a filled portion sized to Value.
type RGBA ¶
type RGBA struct{ R, G, B, A uint8 }
RGBA is a 32-bit colour value. Painters that can't represent a given RGBA (a cell grid limited to 16 colours, for instance) pick the closest supported value.
type Rect ¶
type Rect struct{ X, Y, W, H int }
Rect is a rectangle in the painter's coordinate system. toolkit aliases its own Rect to this one, so the method set is shared — keep any helpers a consumer might want on it (Contains, etc.) defined here.
type Theme ¶
Theme is the palette every widget consults. The prototype ships a minimum viable set — a production merge into toolkit reuses the full go-widgets/toolkit theme struct.
func DarkTheme ¶
func DarkTheme() *Theme
DarkTheme mirrors the go-widgets/toolkit default dark palette.
func LightTheme ¶
func LightTheme() *Theme
LightTheme mirrors the go-widgets/toolkit default light palette.
type Translator ¶ added in v0.4.0
type Translator interface {
PushTranslate(dx, dy int)
PopTranslate()
}
Translator is an optional Painter capability: while a translation is pushed, every coordinate handed to the painter is shifted by it before anything is clipped or written.
Together with Clipper it is a VIEWPORT — the pair a scrolling or panning widget needs. Clipping alone was not enough, and the gap had a real cost: with no way to say "draw my child 250 pixels higher", Clipper's only customer, ScrollView, moved the child's BOUNDS instead, drew, and put them back. Geometry that changes for the duration of a paint is invisible to anything reading it from outside — a screen reader was told a control sat a quarter of a window below where it was painted.
A translation shifts the PAINT, not the widget: a child still lays out and reports its bounds wherever it genuinely is, and the viewport decides where those pixels land. Nothing has to be moved and restored.
if t, ok := p.(painter.Translator); ok {
t.PushTranslate(-offsetX, -offsetY)
defer t.PopTranslate()
}
child.Draw(p, theme)
Translations nest: each push adds to the enclosing one, so a scrolled list inside a scrolled panel behaves as a reader would expect. A clip pushed while a translation is active is translated too, since the caller expresses it in the same coordinates as everything else it draws.
Both PixelPainter and CellPainter implement Translator; a back-end that cannot translate simply does not, and the assertion is skipped — the same contract Clipper uses.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
tui-demo
command
tui-demo renders the same three widgets as wui-demo, but into a CellPainter, and writes the resulting 24-bit-ANSI stream to stdout.
|
tui-demo renders the same three widgets as wui-demo, but into a CellPainter, and writes the resulting 24-bit-ANSI stream to stdout. |
|
wui-demo
command
wui-demo renders the prototype's three widgets into a PixelPainter and writes the resulting RGBA buffer as a PNG.
|
wui-demo renders the prototype's three widgets into a PixelPainter and writes the resulting RGBA buffer as a PNG. |
|
wui-wasm
command
|