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 ¶
- type Button
- type Cell
- type CellPainter
- func (p *CellPainter) FillRect(r Rect, c RGBA)
- func (p *CellPainter) FillRoundRect(r Rect, radius int, c RGBA)
- 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 Label
- type Painter
- type PixelPainter
- func (p *PixelPainter) FillRect(r Rect, c RGBA)
- func (p *PixelPainter) FillRoundRect(r Rect, radius int, c RGBA)
- func (p *PixelPainter) PutPixel(x, y int, c RGBA)
- func (p *PixelPainter) Size() (int, int)
- 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 Widget
Constants ¶
This section is empty.
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) 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) 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 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 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
}
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 (*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.
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) 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) 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.
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
|