Graphite

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 22 Imported by: 0

README

Graphite

Graphite TUI banner

go version license

A small terminal UI (TUI) widget library for Go: a double-buffered canvas with a diffing renderer and truecolor (24-bit RGB) rendering, a Widget interface with focus/enabled/visible state handled for you, a Flex layout container, and a set of ready-made widgets (labels, buttons, checkboxes, input boxes, text areas, list boxes, todo lists, tabs, progress bars, panels for layout, modal windows, a mixing-console Fader, and a playable PianoRoll keyboard with real MIDI and audio companion packages).

Quickstart

git clone <this-repo-url>
cd graphite
go run ./showcase   # every widget, Flex layout, and a custom theme in one window
go run ./gphedit     # a GPH image editor/converter built with Graphite
go run ./promo       # an animated logo/splash screen

Minimal usage from your own program:

package main

import "github.com/yeoblyv/graphite"

func main() {
	app := Graphite.NewApplication()

	win := Graphite.NewWindow(50, 10, " Hello ")
	win.AddWidget(Graphite.NewLabel(2, 2, "Hello, terminal!"))
	win.AddWidget(Graphite.NewButton(2, 4, "Quit", Graphite.BtnDefault, func() {
		app.Quit()
	}))

	app.SetWindow(win)
	app.Run()
}

Esc quits the application (or closes the topmost modal, if one is open); Tab cycles focus; arrow keys and mouse clicks both work out of the box.

Documentation

Full documentation lives in docs/:

Document Covers
Getting started Install, minimal program, running the example programs
Architecture Application, Canvas, Window, Widget/BaseWidget, the render/input loop, concurrency
Layout Fixed/percentage positioning, the stretch rule, Panel, Flex, GroupBox
Theming Color, Theme, DefaultTheme, building a custom palette
Events Event, EventType, KeyCode, mouse/keyboard routing
Widgets reference Every widget except Fader and modals: Label, Button, InputBox, ListBox, TabView, Slider, etc.
Fader The channel-strip mixer control, in depth
PianoRoll The playable piano keyboard, plus the graphite/audio and graphite/midi companion packages for real sound and real MIDI hardware
Modals The modal stack, ShowMessage, ShowValueEditor, ShowFilePicker
Images The GPH pseudographics image format and the Image widget
Custom widgets Building your own widget by embedding BaseWidget
Windows terminal notes The conhost.exe virtual-terminal-processing fix, and why it's needed

Building

This repo ships two ways to build, so you don't need to install anything you don't already have:

  • make (Linux/macOS/CI, or Windows with Make installed):
    make build          # compile everything for the host platform
    make build-all       # cross-compile the example programs for
                          # windows/amd64, windows/386, linux/amd64, linux/386
    make test            # go test ./...
    make lint             # golangci-lint, if installed
    
  • PowerShell (native on Windows, no extra tools):
    ./build.ps1                      # cross-compile for all four targets
    ./build.ps1 -Target linux-amd64  # a single target
    ./build.ps1 -Target Test         # go test ./...
    

Both write cross-compiled binaries to dist/<goos>_<goarch>/.

Architecture at a glance

Application owns the terminal, the canvas, the active window, and the modal stack, and drives the render/input loop (Run) — the one composition root a program constructs, with no package-level mutable state to reason about. Canvas is a double-buffered grid of cells that Render diffs each frame, writing only what changed. Widget is the interface every UI element implements; BaseWidget handles the mechanical parts (layout resolution, focus/enabled/visible state) so concrete widgets only implement drawing and event handling. Window hosts a widget tree, resolves Tab order, and routes mouse/keyboard events, including the implicit mouse capture that lets a drag continue past a widget's own bounds — the same model every desktop GUI toolkit uses. Flex distributes space among children along one axis by weight, CSS-flexbox-style. Fader is a full mixing-console channel strip (gain, an independent VU meter, a clip indicator, Mute/Solo). PianoRoll is a playable piano keyboard (mouse, PC keyboard, or real MIDI hardware via the separate graphite/midi package), with graphite/audio providing real sound. Updating a widget from a background goroutine must go through Application.Invoke, the only thread-safe way to touch widget state from outside the render loop.

Full detail on every one of these — including worked examples — is in docs/ above, starting with Architecture.

Security notes

Two things worth knowing about this library's threat model:

  • Untrusted text is sanitized before it reaches the terminal. Canvas.DrawCell replaces any single-rune string that's a raw control character with a space before writing it, so a widget displaying untrusted content (e.g. subprocess output streamed into a TextArea) can't have that content inject arbitrary ANSI/OSC escape sequences — title-bar spoofing, color/state resets, or worse — into the user's real terminal. Multi-rune strings (the box-drawing/block-element glyphs widgets themselves draw) pass through unchanged, since those come from trusted library code, not user-supplied data.
  • Widget selection indices are bounds-checked against the current data, not assumed from a stale clamp — ListBox/TodoList/etc. re-validate Selected against the live length of Items at the point of use, so a caller reassigning Items to a shorter slice at runtime (e.g. re-filtering a list) can't leave Selected pointing past the end and cause an out-of-bounds read.
  • This library does not protect against a malicious terminal emulator — it assumes the terminal it's talking to correctly implements the SGR mouse/color/alternate-screen sequences it uses and isn't itself hostile. It also does not sanitize file paths passed to ShowFilePicker/gph.go's file I/O helpers beyond what the OS itself enforces — treat paths from those APIs the same as any other filesystem path your program handles.

Contributing

See CONTRIBUTING.md.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var GphMagic = []byte{'G', 'P', 'H', '\x01'}

GphMagic is the expected file signature for version 1 of the GPH format.

Functions

func GetTerminalSize

func GetTerminalSize() (int, int)

GetTerminalSize returns the current width and height of the terminal, or an 80x24 fallback if the size cannot be determined (e.g. stdout is not a terminal).

func NoteName

func NoteName(note uint8) string

NoteName formats a MIDI note number (0-127) as a pitch class plus octave number, e.g. NoteName(60) == "C4", using the widely used convention where note 60 (middle C) is octave 4.

func ShowConfirm

func ShowConfirm(app *Application, title, message string, style ButtonStyle, onConfirm func())

ShowConfirm opens a modal titled title asking the user to confirm message with "Yes"/"No" buttons. onConfirm runs only if "Yes" is chosen; either button closes the modal. style colors the "Yes" button — pass BtnDanger for a destructive confirmation (e.g. delete), BtnDefault for a neutral one.

func ShowFilePicker

func ShowFilePicker(app *Application, initialDir string, onSelect func(path string))

ShowFilePicker opens a modal dialog that lets the user browse the filesystem and select a file. The callback onSelect is invoked with the absolute path of the chosen file. If the user cancels, the modal is closed and onSelect is not called.

func ShowTextEditor

func ShowTextEditor(app *Application, title, label, current string, onConfirm func(string))

ShowTextEditor opens a modal titled title prompting for a single line of free-form text via label, pre-filled with current. It is ShowValueEditor's non-numeric counterpart, for callers that need an arbitrary string (a file or directory name) rather than a bounded number.

Both the "OK" button and Enter inside the field (via InputBox.OnSubmit) confirm; a blank (whitespace-only) value shows an inline error and leaves the modal open instead of calling onConfirm, since every known caller (naming a file or directory) requires a non-blank result. "Cancel" closes without calling onConfirm.

func ShowValueEditor

func ShowValueEditor(app *Application, title string, current, min, max float64, onConfirm func(float64))

ShowValueEditor opens a modal titled title with an input field pre-filled with current. If the user submits a valid number, the modal closes and onConfirm is called; otherwise, an error modal stacks on top of it.

func WriteGph

func WriteGph(w io.Writer, img *GphImage) error

WriteGph serializes the image into the GPH binary format. It uses bitmask-based delta encoding for frames after the first one.

Types

type Application

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

Application owns the terminal, the canvas, the active window and modal stack, and drives the main render/input loop via Run. It is the single composition root a program constructs: all state that would otherwise need to be global (terminal mode, color theme) lives on this struct instead.

func NewApplication

func NewApplication() *Application

NewApplication creates an Application with an empty canvas using DefaultTheme. Call SetTheme afterwards to customize colors.

func (*Application) CloseModal

func (app *Application) CloseModal()

CloseModal closes the topmost modal, revealing the previous one (if any) or the active window.

func (*Application) Invoke

func (app *Application) Invoke(fn func())

Invoke queues fn to run on the main loop just before the next frame is drawn. This is the only safe way to touch widget state from a background goroutine: mutating a widget's fields directly from another goroutine races with Run's render loop reading those same fields.

func (*Application) Quit

func (app *Application) Quit()

Quit stops Run after the current frame.

func (*Application) Resume

func (app *Application) Resume()

Resume switches the terminal back into raw/TUI mode and forces a full redraw, e.g. after an interactive subprocess launched via Suspend exits.

func (*Application) Run

func (app *Application) Run()

Run initializes the terminal and drives the main loop: resize, drain queued Invoke callbacks, draw, render, poll for input, and route the resulting event, until Quit is called or the process is torn down. The terminal is always restored on return, including on panic.

func (*Application) SetIdleCallback

func (app *Application) SetIdleCallback(cb func())

SetIdleCallback sets a function invoked repeatedly once 300ms have elapsed with no input, useful for polling background state (see components/erbe-3100-tester for an example).

func (*Application) SetModal

func (app *Application) SetModal(mod *Window)

SetModal opens mod on top of the current modal stack, leaving any already-open modal in place beneath it.

func (*Application) SetOnQuitRequested

func (app *Application) SetOnQuitRequested(fn func())

SetOnQuitRequested overrides what Escape does when no modal is open: instead of quitting immediately, Run calls fn and leaves the application running. fn is responsible for deciding whether to quit — typically by opening a ShowConfirm dialog whose "Yes" button calls Quit. Passing nil restores the default immediate-quit behavior.

func (*Application) SetStatus

func (app *Application) SetStatus(status string)

SetStatus sets the text shown in the status bar along the bottom row.

func (*Application) SetTheme

func (app *Application) SetTheme(t Theme)

SetTheme replaces the color palette used to render every widget and forces a full redraw so the change is visible on the next frame.

func (*Application) SetWindow

func (app *Application) SetWindow(win *Window)

SetWindow sets the non-modal window drawn behind any open modals.

func (*Application) ShowMessage

func (app *Application) ShowMessage(title, message string, style ButtonStyle)

ShowMessage opens a modal dialog with a title, a message, and a single OK button styled per style. It is layered on SetModal, so it stacks on top of any modal already open rather than replacing it.

func (*Application) Suspend

func (app *Application) Suspend()

Suspend restores the terminal to normal (cooked) mode, e.g. before shelling out to an interactive subprocess.

type BaseWidget

type BaseWidget struct {
	X, Y, Width, Height                      int
	PctX, PctY, PctW, PctH                   int
	IsFocusable, IsFocused, Enabled, Visible bool
	AbsX, AbsY, LastW, LastH                 int
}

BaseWidget implements the mechanical parts of Widget (layout resolution, focus/enabled/visible state) so concrete widgets only need to implement drawing and event handling.

func NewBaseWidget

func NewBaseWidget(x, y, w, h int) BaseWidget

NewBaseWidget creates a BaseWidget at the given fixed position and size, enabled and visible by default. Percent-based layout, if wanted, is added afterwards via SetPercentLayout.

func (*BaseWidget) CanFocus

func (b *BaseWidget) CanFocus() bool

CanFocus reports whether this widget is eligible to receive keyboard focus: it must be focusable by design, enabled, and visible.

func (*BaseWidget) DrawOverlay

func (b *BaseWidget) DrawOverlay(c *Canvas, oX, oY, pW, pH int)

DrawOverlay draws content that must appear above sibling widgets (e.g. dropdown popups). The default implementation draws nothing.

func (*BaseWidget) DrawRelative

func (b *BaseWidget) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative resolves the widget's absolute position and size (AbsX, AbsY, LastW, LastH) against the parent's content origin (offX, offY) and dimensions (pW, pH). It performs no drawing itself; concrete widgets call this first, then draw using the resolved fields.

func (*BaseWidget) GetChildren

func (b *BaseWidget) GetChildren() []Widget

GetChildren returns nested widgets so Window can flatten containers (e.g. Panel) when building the focus order and hit-testing the tree. Leaf widgets have none.

func (*BaseWidget) GetFixedH

func (b *BaseWidget) GetFixedH() int

GetFixedH returns the widget's configured height, or 1 if none was set. Containers that stack children vertically by their natural size (e.g. Flex) use this for children not given a proportional weight.

func (*BaseWidget) GetFixedW

func (b *BaseWidget) GetFixedW() int

GetFixedW returns the widget's configured width, or 1 if none was set. Symmetric with GetFixedH, for containers (e.g. Flex) laying out children horizontally by their natural size.

func (*BaseWidget) HandleEvent

func (b *BaseWidget) HandleEvent(ev Event)

HandleEvent processes an input event routed to this widget. The default implementation ignores all events.

func (*BaseWidget) HasFocus

func (b *BaseWidget) HasFocus() bool

HasFocus reports whether this widget currently has keyboard focus.

func (*BaseWidget) HitTest

func (b *BaseWidget) HitTest(mx, my int) bool

HitTest reports whether the screen coordinates (mx, my) fall within the widget's last resolved bounds.

func (*BaseWidget) IsEnabled

func (b *BaseWidget) IsEnabled() bool

IsEnabled reports whether this widget currently accepts input.

func (*BaseWidget) IsVisible

func (b *BaseWidget) IsVisible() bool

IsVisible reports whether this widget is currently visible.

func (*BaseWidget) SetEnabled

func (b *BaseWidget) SetEnabled(e bool)

SetEnabled sets whether this widget accepts input. Window routes no events to a disabled widget, regardless of what HandleEvent does.

func (*BaseWidget) SetFocus

func (b *BaseWidget) SetFocus(f bool)

SetFocus sets whether this widget currently has keyboard focus.

func (*BaseWidget) SetPercentLayout

func (b *BaseWidget) SetPercentLayout(x, y, w, h int)

SetPercentLayout switches the widget to percentage-based positioning and sizing relative to its parent's content area. A value of 0 for any axis keeps the corresponding fixed X/Y/Width/Height instead.

func (*BaseWidget) SetPosition

func (b *BaseWidget) SetPosition(x, y int)

SetPosition sets the widget's fixed X/Y offset used by DrawRelative.

func (*BaseWidget) SetVisible

func (b *BaseWidget) SetVisible(v bool)

SetVisible sets whether this widget is drawn and eligible for focus/hit testing.

type Button

type Button struct {
	BaseWidget
	Text    string
	Style   ButtonStyle
	OnClick func()
	// BgColor/FgColor override the button's idle (enabled, unfocused,
	// non-BtnDanger) colors; ColorNone (the default set by NewButton)
	// uses theme.BgWidget/FgWindow instead, matching every Button's
	// original appearance. Focused, disabled, and BtnDanger-while-
	// unfocused rendering are unaffected — a caller wanting a button that
	// reads as its own accent even when idle (e.g. a toolbar button that
	// would otherwise blend into a plain list background) sets these
	// instead of only being able to distinguish it once focused.
	BgColor Color
	FgColor Color
}

Button is a focusable, clickable action with a text label.

func NewButton

func NewButton(x, y int, text string, style ButtonStyle, onClick func()) *Button

NewButton creates a Button at (x, y) that calls onClick when activated, with BgColor/FgColor left at their default (ColorNone, meaning "use the theme").

func (*Button) DrawRelative

func (b *Button) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Button) HandleEvent

func (b *Button) HandleEvent(ev Event)

HandleEvent implements Widget: Enter and mouse clicks both activate OnClick. Window.HandleEvent already withholds events from a disabled button, so no enabled check is needed here.

type ButtonStyle

type ButtonStyle int

ButtonStyle selects a Button's accent color.

const (
	BtnDefault ButtonStyle = iota
	BtnSuccess
	BtnDanger
	BtnWarning
	BtnInfo
)

Supported button styles.

type Canvas

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

Canvas is a double-buffered terminal grid. Widgets draw into the back buffer; Render diffs it against the front buffer and emits only the ANSI sequences needed to bring the terminal up to date.

func NewCanvas

func NewCanvas() *Canvas

NewCanvas creates an empty Canvas using DefaultTheme. Call Resize before drawing to it; Application does this automatically on every frame.

func (*Canvas) Clear

func (c *Canvas) Clear()

Clear resets every cell in the back buffer to the screen background, ready for the next frame's widgets to draw over it.

func (*Canvas) DrawCell

func (c *Canvas) DrawCell(x, y int, s string, bg, fg Color)

DrawCell writes a single glyph at (x, y). Coordinates outside the canvas are silently ignored so widgets do not need to bounds-check every write. Passing ColorNone for bg or fg leaves that color unchanged. s is assumed to occupy exactly one terminal column — every direct caller in this package already passes single-column glyphs (box drawing, block elements); free-form text should go through DrawText instead, which accounts for double-width runes.

func (*Canvas) DrawText

func (c *Canvas) DrawText(x, y int, text string, bg, fg Color)

DrawText writes text starting at (x, y), advancing one column for a normal-width rune and two for a double-width one (e.g. CJK), so callers don't need to compute display width themselves.

func (*Canvas) DrawTextBounded

func (c *Canvas) DrawTextBounded(x, y, maxW int, text string, bg, fg Color)

DrawTextBounded writes text like DrawText, but truncates it and appends "…" if it would exceed maxW columns.

func (*Canvas) DrawTextWrapped

func (c *Canvas) DrawTextWrapped(x, y, maxW int, text string, bg, fg Color) int

DrawTextWrapped writes text, wrapping on whitespace if it exceeds maxW columns. Returns the number of lines drawn.

func (*Canvas) GetCellBg

func (c *Canvas) GetCellBg(x, y int) Color

GetCellBg returns the background color at (x, y), or the theme's screen background for out-of-bounds coordinates. Widgets use this to blend decorations (e.g. window shadows) with whatever is already underneath.

func (*Canvas) Height

func (c *Canvas) Height() int

Height returns the canvas height in rows.

func (*Canvas) Render

func (c *Canvas) Render()

Render diffs the back buffer against what was last drawn to the terminal and writes only the changed cells, minimizing the bytes sent per frame. A resize or the first frame forces every cell to be rewritten.

func (*Canvas) Resize

func (c *Canvas) Resize(w, h int)

Resize changes the canvas dimensions, reallocating both buffers and forcing a full redraw on the next Render. It is a no-op when the dimensions are unchanged, since terminal size is polled every frame.

func (*Canvas) Theme

func (c *Canvas) Theme() Theme

Theme returns the color palette this Canvas currently renders with, so a custom widget defined outside package Graphite (see docs/custom-widgets.md) can read live theme colors in its own DrawRelative the same way every built-in widget reads c.theme — instead of only being able to capture colors once, at construction time, from whatever Theme its caller happened to have on hand.

func (*Canvas) Width

func (c *Canvas) Width() int

Width returns the canvas width in columns.

type Cell

type Cell struct {
	Symbol  string
	BgColor Color
	FgColor Color
	// contains filtered or unexported fields
}

Cell is a single terminal character position: its glyph plus foreground and background color.

func (Cell) NotEqual

func (c Cell) NotEqual(other Cell) bool

NotEqual reports whether c would render differently from other.

type Checkbox

type Checkbox struct {
	BaseWidget
	Label    string
	Checked  bool
	OnChange func(checked bool)
}

Checkbox is a focusable boolean toggle with a label.

func NewCheckbox

func NewCheckbox(x, y int, label string, checked bool) *Checkbox

NewCheckbox creates a Checkbox at (x, y) with the given initial state.

func (*Checkbox) DrawRelative

func (cb *Checkbox) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Checkbox) HandleEvent

func (cb *Checkbox) HandleEvent(ev Event)

HandleEvent implements Widget: Space, Enter, and mouse clicks all toggle the checkbox.

type Color

type Color int32

Color is a 24-bit truecolor value, packed as (R<<16)|(G<<8)|B. Canvas.Render emits it as a truecolor ANSI SGR sequence, which every terminal capable of running this library already supports (rendering here already depends on the alternate screen buffer and SGR mouse mode, both modern-terminal-only features — there is no legacy 16/256-color fallback path).

const ColorNone Color = -1

ColorNone means "leave the existing color unchanged" — passed to Canvas.DrawCell/DrawText for bg or fg to only touch the other one.

func Hex

func Hex(s string) Color

Hex parses a "#RRGGBB" or "RRGGBB" string into a Color. It panics on a malformed string, since a bad hex literal is a programming error to be caught at development time, not a runtime condition to recover from.

func RGB

func RGB(r, g, b uint8) Color

RGB builds a Color from 8-bit red, green, and blue components.

func (Color) Components

func (c Color) Components() (r, g, b uint8)

Components unpacks the red, green, and blue bytes of c, for callers that need to inspect or recombine a color (e.g. picking a readable text color against an arbitrary background).

func (Color) ContrastText

func (c Color) ContrastText() Color

ContrastText returns black or white, whichever reads better as text on top of c, using perceived luminance (ITU-R BT.601: 0.299R + 0.587G + 0.114B). This is the exact computation showcase's own contrastText helper (see docs/custom-widgets.md) already duplicated locally; it lives here now so a widget that colors itself from an arbitrary or caller-supplied background — not just a fixed theme field — doesn't have to re-derive it.

func (Color) Darken

func (c Color) Darken(pct float64) Color

Darken returns c scaled towards black by pct (0 keeps c unchanged, 1 returns black). It replaces arithmetic like "subtract 10 from an ANSI code", which has no equivalent once colors are RGB instead of palette indices — e.g. a button wants a dimmer variant of its Danger color when unfocused.

type ComboBox

type ComboBox struct {
	BaseWidget
	Items    []string
	Selected int
	IsOpen   bool
	OnSelect func(idx int, item string)
}

ComboBox is a focusable dropdown menu.

func NewComboBox

func NewComboBox(x, y, w int, items []string, onSelect func(int, string)) *ComboBox

NewComboBox creates a ComboBox at (x, y) with the given fixed width.

func (*ComboBox) DrawOverlay

func (cb *ComboBox) DrawOverlay(c *Canvas, offX, offY, pW, pH int)

DrawOverlay draws the expanded dropdown list if IsOpen is true.

func (*ComboBox) DrawRelative

func (cb *ComboBox) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative draws the closed state of the ComboBox.

func (*ComboBox) HandleEvent

func (cb *ComboBox) HandleEvent(ev Event)

HandleEvent processes input.

func (*ComboBox) HitTest

func (cb *ComboBox) HitTest(mx, my int) bool

HitTest overrides BaseWidget.HitTest to expand the hit area when open.

type Event

type Event struct {
	Type     EventType
	Key      KeyCode
	CharCode rune
	MouseX   int
	MouseY   int
}

Event is a single input notification delivered to the focused widget (for keyboard input) or to whichever widget is hit-tested under the pointer (for mouse input).

type EventType

type EventType int

EventType discriminates the kind of input an Event carries.

const (
	EventNone EventType = iota
	EventKey
	EventMouseDown
	// EventMouseDrag is a motion report with the left button held, deliv-
	// ered only to whichever widget was hit by the preceding EventMouseDown
	// (see Window's mouse capture), regardless of where the pointer moves.
	EventMouseDrag
	// EventMouseUp is a button release. Like EventMouseDrag, it goes to the
	EventMouseUp
	EventMouseScrollUp
	EventMouseScrollDown
	// EventMouseRightDown is a right-button press. Unlike EventMouseDown it
	// doesn't move focus or start a mouse capture (there's no corresponding
	// "right button held" drag or release to capture for) — it's simply
	// hit-tested and delivered once, the same as a scroll event, for a
	// widget that wants a secondary click-driven action (e.g. a
	// context-menu trigger, or a quick toggle) distinct from its primary
	// EventMouseDown behavior.
	EventMouseRightDown
)

Supported event kinds.

type Fader

type Fader struct {
	BaseWidget

	ChannelName string
	LabelColor  Color

	Value float64 // 0-100, the fader's own gain position.
	Level float64 // 0-100, independent live VU level; see SetLevel.

	Clipping      bool
	ClipThreshold float64 // Level at/above which SetLevel latches Clipping. Zero means 100.

	ShowMeter bool
	ShowClip  bool
	ShowMute  bool
	ShowSolo  bool

	Muted  bool
	Soloed bool

	Ticks []FaderTick

	OnChange      func(value float64)
	OnMuteChange  func(muted bool)
	OnSoloChange  func(soloed bool)
	OnDoubleClick func()
	// contains filtered or unexported fields
}

Fader is a vertical channel-strip control: a draggable, clickable gain fader (Value, 0-100), an optional independent VU meter (Level, set via SetLevel — distinct from Value, the way a real mixer's meter shows the actual signal while the fader only sets gain), an optional latching clip LED, and optional icon-only Mute/Solo buttons sharing one row, and a colored channel label. Height defaults to 0, so — like Panel — a Fader stretches to fill whatever vertical space its parent (e.g. Flex) offers; set Height explicitly for a fixed size instead.

func NewFader

func NewFader(x, y, w int, channelName string, labelColor Color) *Fader

NewFader creates a Fader at (x, y) with the given width, channel name, and label color; Height starts at 0 (stretch to fill the parent — see the Fader doc comment). All optional sub-features (meter, clip LED, mute, solo) start enabled; Value starts at 80 (near the "0" mark on the default ticks, matching where a real fader normally sits — not pinned at the top).

func (*Fader) ClearClip

func (f *Fader) ClearClip()

ClearClip resets the latched clip indicator.

func (*Fader) DrawRelative

func (f *Fader) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Fader) HandleEvent

func (f *Fader) HandleEvent(ev Event)

HandleEvent implements Widget: arrow keys nudge Value, a track click jumps to that position (or fires OnDoubleClick on a fast second click at the same spot), a drag continues updating Value the same way a click would — Window's mouse capture guarantees Fader keeps receiving EventMouseDrag even once the pointer leaves its own bounds — and clicks on the clip LED clear it, and clicks on the buttons row toggle Mute or Solo depending on which half was hit.

func (*Fader) SetLevel

func (f *Fader) SetLevel(level float64)

SetLevel sets the independent live VU level (0-100), clamped, and lights Clipping while it reaches ClipThreshold — by default, the same point the meter turns red (faderDangerZonePercent), not only a full 100% peak. Clipping turns off automatically when Level drops back down.

type FaderTick

type FaderTick struct {
	Label   string
	Percent float64 // 0-100, where 100 is the top of the track.
}

FaderTick is one labeled position along a Fader's scale. It is purely a visual label — this library has no real audio pipeline, so Percent is just where along the 0-100 track the label is drawn, not a dB conversion.

type Flex

type Flex struct {
	BaseWidget
	Direction FlexDirection
	// Gap is the number of columns/rows of empty space inserted between
	// consecutive visible children.
	Gap int
	// contains filtered or unexported fields
}

Flex is a layout container that distributes space among its children along one axis, CSS-flexbox-style, instead of requiring the caller to compute fixed pixel or percentage offsets by hand. A child with Weight <= 0 gets its own natural size (GetFixedW for FlexRow, GetFixedH for FlexColumn); children with a positive weight split whatever space is left over, proportional to their weight relative to the other weighted children. The cross axis always stretches a child to the container's full size, matching Panel's existing behavior.

func NewFlex

func NewFlex(x, y, w, h int, dir FlexDirection) *Flex

NewFlex creates an empty Flex container at (x, y) with the given size and direction.

func (*Flex) AddChild

func (f *Flex) AddChild(w Widget, weight int)

AddChild appends a child widget. weight <= 0 sizes it to its own natural size along the main axis; a positive weight instead claims that proportion of the space remaining after every fixed-size sibling.

The weighted share is the box Flex offers the child via DrawRelative — whether the child actually fills it follows the same rule every widget already follows: a widget with a positive fixed Width/Height keeps that size regardless of the offered box (e.g. Label sizes itself to its text), while one left at Width/Height <= 0, or using SetPercentLayout, stretches to fill it. Use Panel (or a nested Flex) as the weighted child when the content itself should grow to fill the allotted space.

func (*Flex) Clear

func (f *Flex) Clear()

Clear removes every child, for containers that rebuild their contents at runtime (e.g. a dynamic list of channels) — clear and re-add is simpler and less error-prone than tracking individual child indices.

func (*Flex) DrawOverlay

func (f *Flex) DrawOverlay(c *Canvas, offX, offY, pW, pH int)

DrawOverlay implements Widget.

func (*Flex) DrawRelative

func (f *Flex) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget. Invisible children are skipped entirely — neither drawn nor given a share of space — so hiding a child closes the gap it would otherwise leave, matching CSS's `display: none`.

func (*Flex) GetChildren

func (f *Flex) GetChildren() []Widget

GetChildren implements Widget, letting Window descend into the container when building the focus order and hit-testing the tree.

type FlexDirection

type FlexDirection int

FlexDirection selects the main axis a Flex container lays its children out along.

const (
	// FlexRow arranges children left-to-right; each spans the container's
	// full height.
	FlexRow FlexDirection = iota
	// FlexColumn arranges children top-to-bottom; each spans the
	// container's full width.
	FlexColumn
)

Supported flex directions.

type GphImage

type GphImage struct {
	Width   int
	Height  int
	Mode    PlaybackMode
	DelayMs uint16
	Frames  [][]GphPixel
}

GphImage contains the dimensions, playback metadata, and frames of a GPH file.

func LoadGphFile

func LoadGphFile(path string) (*GphImage, error)

LoadGphFile is a convenience function to read a GPH image from the filesystem.

func ReadGph

func ReadGph(r io.Reader) (*GphImage, error)

ReadGph parses a GPH binary stream with delta decoding.

type GphPixel

type GphPixel struct {
	Bg    Color
	Fg    Color
	Level uint8 // 0=0%, 1=25%, 2=50%, 3=75%, 4=100% density
}

GphPixel represents a single "pixel" in a console pseudographics image.

type GroupBox

type GroupBox struct {
	BaseWidget
	Label    string
	Children []Widget
}

GroupBox is a Panel that draws a border and a label around its children. Children should be positioned at least at X=1, Y=1 (or Y=2 if they shouldn't overlap the top border).

func NewGroupBox

func NewGroupBox(x, y, w, h int, label string) *GroupBox

NewGroupBox creates an empty GroupBox at (x, y) with the given size and label.

func (*GroupBox) AddWidget

func (gb *GroupBox) AddWidget(w Widget)

AddWidget appends a child widget to the groupbox.

func (*GroupBox) DrawOverlay

func (gb *GroupBox) DrawOverlay(c *Canvas, offX, offY, pW, pH int)

DrawOverlay implements Widget.

func (*GroupBox) DrawRelative

func (gb *GroupBox) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*GroupBox) GetChildren

func (gb *GroupBox) GetChildren() []Widget

GetChildren implements Widget.

type Image

type Image struct {
	BaseWidget
	Img      *GphImage
	AutoSize bool

	OnFrameUpdate func()
	// contains filtered or unexported fields
}

Image is a widget that renders a GphImage.

func NewImage

func NewImage(x, y int, img *GphImage) *Image

NewImage creates an Image widget at (x, y) with the exact dimensions of the provided GphImage. If the image is nil, it creates a 1x1 empty widget.

func (*Image) DrawRelative

func (img *Image) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Image) NextFrame

func (img *Image) NextFrame()

NextFrame advances the animation to the next frame manually.

func (*Image) Play

func (img *Image) Play(app *Application)

Play starts the animation loop if the image has multiple frames. It stops any currently running animation for this widget.

func (*Image) PrevFrame

func (img *Image) PrevFrame()

PrevFrame advances the animation to the previous frame manually.

func (*Image) Stop

func (img *Image) Stop()

Stop halts the animation playback.

type InputBox

type InputBox struct {
	BaseWidget
	Label     string
	Value     string
	CursorPos int
	OnSubmit  func(string)
	// Masked renders every character of Value as "•" instead of itself —
	// for a password or passphrase field — without changing anything
	// about how Value, CursorPos, or editing actually work; HandleEvent
	// still operates on the real string, it just never lets Ctrl+C/Ctrl+X
	// put it on the OS clipboard (see HandleEvent).
	Masked bool
}

InputBox is a single-line, focusable text field with a fixed label, horizontal scrolling, and a visible text cursor.

func NewInputBox

func NewInputBox(x, y, w int, label string) *InputBox

NewInputBox creates an InputBox at (x, y) with the given width and label.

func NewPasswordBox

func NewPasswordBox(x, y, w int, label string) *InputBox

NewPasswordBox creates an InputBox identical to NewInputBox except its contents render as "•" and never reach the OS clipboard via Ctrl+C/Ctrl+X — for a password or passphrase field.

func (*InputBox) DrawRelative

func (ib *InputBox) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*InputBox) HandleEvent

func (ib *InputBox) HandleEvent(ev Event)

HandleEvent implements Widget: arrow keys move the cursor, Backspace and Delete remove the rune behind/under it, and any other printable character is inserted at the cursor. A mouse click moves the cursor to the clicked column, accounting for horizontal scroll.

type KeyCode

type KeyCode int

KeyCode identifies a non-printable key reported by an Event.

const (
	KeyNone      KeyCode = 0
	KeyTab       KeyCode = 9
	KeyEnter     KeyCode = 10
	KeyEscape    KeyCode = 27
	KeySpace     KeyCode = 32
	KeyBackspace KeyCode = 127
	KeyUp        KeyCode = 1001
	KeyDown      KeyCode = 1002
	KeyLeft      KeyCode = 1003
	KeyRight     KeyCode = 1004
	KeyDelete    KeyCode = 1005
	KeyCtrlC     KeyCode = 1006
	KeyCtrlV     KeyCode = 1007
	KeyCtrlX     KeyCode = 1008
	KeyF1        KeyCode = 1009
	KeyF2        KeyCode = 1010
	KeyF3        KeyCode = 1011
	KeyF4        KeyCode = 1012
	KeyF5        KeyCode = 1013
	KeyF6        KeyCode = 1014
	KeyF7        KeyCode = 1015
	KeyF8        KeyCode = 1016
	KeyF9        KeyCode = 1017
	KeyF10       KeyCode = 1018
	KeyF11       KeyCode = 1019
	KeyF12       KeyCode = 1020
	KeyInsert    KeyCode = 1021
)

Recognized non-printable key codes. Printable characters arrive via Event.CharCode instead, with Key left as KeyNone.

type Label

type Label struct {
	BaseWidget
	Text string
}

Label draws a single line of static text.

func NewLabel

func NewLabel(x, y int, text string) *Label

NewLabel creates a Label at (x, y) sized to fit text.

func (*Label) DrawRelative

func (l *Label) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Label) SetText

func (l *Label) SetText(text string)

SetText replaces the label's text and resizes it to fit the new content.

type ListBox

type ListBox struct {
	BaseWidget
	Items    []string
	Selected int
	Scroll   int
	OnSelect func(int, string)

	OnDoubleClick func(int, string)
	// contains filtered or unexported fields
}

ListBox is a focusable, scrollable, single-selection list.

func NewListBox

func NewListBox(x, y, w, h int, items []string, onSelect func(int, string)) *ListBox

NewListBox creates a ListBox at (x, y) listing items. onSelect, if non-nil, is called with the selected index and text on Enter or a mouse click on a row.

func (*ListBox) DrawRelative

func (lb *ListBox) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*ListBox) HandleEvent

func (lb *ListBox) HandleEvent(ev Event)

HandleEvent implements Widget: Up/Down move the selection (scrolling to keep it visible), Enter and a mouse click on a row both fire OnSelect.

type MenuCategory struct {
	Label string
	Items []MenuItem
}
type MenuItem struct {
	Label     string
	Action    func()
	Separator bool
	SubItems  []MenuItem
}

MenuItem is one row in a MenuStrip dropdown (or a nested SubItems flyout): a plain clickable item (Label+Action), a separator (Separator true; Label/Action/SubItems all ignored), or a submenu (SubItems non-empty; Action ignored, clicking instead opens a nested flyout of SubItems next to it). Only one level of nesting is supported — an item inside SubItems with its own SubItems is not opened.

type MenuStrip struct {
	BaseWidget
	Categories []MenuCategory
	OpenIdx    int
	// OpenSubIdx is the index within Categories[OpenIdx].Items whose
	// SubItems flyout is currently open, or -1 if none is.
	OpenSubIdx int
	// BgColor overrides the strip's (and its open dropdown's) background;
	// ColorNone (the default set by NewMenuStrip) uses the theme's
	// BgWidget/BgWindow instead, matching the strip's original
	// appearance. FgColor overrides the text color; ColorNone auto-picks
	// black or white for contrast against BgColor (via Color.ContrastText)
	// once BgColor is itself set, or falls back to the theme's FgWindow
	// when neither is set.
	BgColor Color
	FgColor Color
}

MenuStrip is a top-level horizontal bar containing clickable categories that open dropdowns.

func NewMenuStrip

func NewMenuStrip(categories []MenuCategory) *MenuStrip

NewMenuStrip creates a MenuStrip spanning the full width of whatever contains it, with BgColor/FgColor left at their default (ColorNone, meaning "use the theme").

func (m *MenuStrip) DrawOverlay(c *Canvas, offX, offY, pW, pH int)

DrawOverlay implements Widget.

func (m *MenuStrip) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (m *MenuStrip) HandleEvent(ev Event)

HandleEvent implements Widget: clicking a category toggles its dropdown open/closed; clicking a plain item in an open dropdown (or submenu) runs its Action and closes everything; clicking a submenu item (one with SubItems) toggles that submenu instead, leaving the dropdown open; clicking a separator does nothing. Anything else — a click outside every open surface — closes everything, the dropdown's original dismiss-on-outside-click behavior.

func (m *MenuStrip) HitTest(mx, my int) bool

HitTest overrides BaseWidget.HitTest to capture all clicks while a menu is open.

type Panel

type Panel struct {
	BaseWidget
	Children []Widget
	FocusIdx int
}

Panel groups child widgets under a shared position and size (typically percentage-based) without being focusable itself; only its children are.

func NewPanel

func NewPanel(x, y, w, h int) *Panel

NewPanel creates an empty Panel at (x, y) with the given size.

func (*Panel) AddWidget

func (p *Panel) AddWidget(w Widget)

AddWidget appends a child widget to the panel.

func (*Panel) DrawOverlay

func (p *Panel) DrawOverlay(c *Canvas, offX, offY, pW, pH int)

DrawOverlay implements Widget.

func (*Panel) DrawRelative

func (p *Panel) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Panel) GetChildren

func (p *Panel) GetChildren() []Widget

GetChildren implements Widget, letting Window descend into the panel when building the focus order and hit-testing the tree.

type PianoOrientation

type PianoOrientation int

PianoOrientation selects which axis a PianoRoll lays its keys along.

const (
	// PianoHorizontal lays keys left-to-right, lowest note on the left —
	// a conventional piano keyboard.
	PianoHorizontal PianoOrientation = iota
	// PianoVertical lays keys bottom-to-top, lowest note at the bottom —
	// matching the pitch axis of a piano-roll editor or a vertically
	// mounted keyboard controller.
	PianoVertical
)

Supported PianoRoll orientations.

type PianoRoll

type PianoRoll struct {
	BaseWidget

	Orientation PianoOrientation

	// MinKeys is the minimum number of consecutive MIDI notes (white and
	// black combined) always shown, starting at LowestNote — a floor, not
	// a cap. If the widget is offered more space than MinKeys needs, more
	// notes are added automatically, extending upward in pitch. 25, 37,
	// 49, 61, 76, and 88 are the sizes real MIDI keyboard controllers
	// ship in (2/3/4/5/6.5/7.25 octaves respectively); 25 is a sensible
	// floor matching the smallest common controller.
	MinKeys int

	// LowestNote is the MIDI note number (0-127, 60 = middle C) of the
	// leftmost (horizontal) or bottommost (vertical) key when exactly
	// MinKeys are shown.
	LowestNote uint8

	// KeyMap maps a lowercased typed rune to a semitone offset from
	// KeyboardOctaveBase. Defaults to defaultPianoKeyMap(); replace it
	// for a different layout, or set entries to remap individual keys.
	KeyMap map[rune]int
	// KeyboardOctaveBase is the MIDI note KeyMap's offset 0 corresponds
	// to. Defaults to LowestNote at construction.
	KeyboardOctaveBase uint8

	// Velocity is used for notes triggered by mouse or PC-keyboard, which
	// have no natural velocity of their own (unlike a real MIDI
	// controller, which reports how hard a key was struck).
	Velocity uint8

	// KeyReleaseTimeout is how long a PC-keyboard-triggered note keeps
	// sounding after the last repeat event for its key, before being
	// treated as released. Raw terminal input has no true key-up event —
	// only a stream of key-down/repeat bytes — so this is a heuristic,
	// not an exact measurement: the OS's keyboard repeat rate must be
	// faster than this timeout for a held key to sustain correctly.
	// Defaults to 150ms, which comfortably outlasts every common OS
	// repeat rate without noticeably delaying release on key-up.
	KeyReleaseTimeout time.Duration

	// OnNoteOn fires the instant a note starts sounding from any source
	// (mouse, keyboard, or a NoteOn call from outside, e.g. real MIDI
	// input) — not once per source, so mousing down on a key already
	// held via the keyboard does not re-trigger it.
	OnNoteOn func(note uint8, velocity uint8)
	// OnNoteOff fires the instant a note stops sounding from every
	// source that was holding it.
	OnNoteOff func(note uint8)
	// contains filtered or unexported fields
}

PianoRoll is a piano-keyboard widget: it renders a standard white/black key pattern (horizontally or vertically), shows at least MinKeys notes and adds more automatically as its parent offers it more space, and can be played by mouse clicks, the PC keyboard, or driven programmatically — which is how a real MIDI input device is wired in (see the graphite/midi package). Sound is not part of this widget: wire OnNoteOn/OnNoteOff to a synth (see the graphite/audio package) or your own audio code.

func NewPianoRoll

func NewPianoRoll(x, y, w, h int, orientation PianoOrientation, lowestNote uint8) *PianoRoll

NewPianoRoll creates a PianoRoll at (x, y) with the given width/height (either may be <= 0 to stretch — see layout.md), showing at least 25 keys starting at lowestNote.

func (*PianoRoll) DrawRelative

func (p *PianoRoll) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*PianoRoll) HandleEvent

func (p *PianoRoll) HandleEvent(ev Event)

HandleEvent implements Widget: a mouse press/drag plays whichever key is under the pointer (dragging across keys glissandos, releasing the previous key first), a mapped keyboard character plays its note for as long as it keeps repeating (see KeyReleaseTimeout), and Left/Right nudge LowestNote by one semitone, transposing the whole keyboard.

func (*PianoRoll) NoteOff

func (p *PianoRoll) NoteOff(note uint8)

NoteOff releases note from the external source. See NoteOn for the goroutine-safety note.

func (*PianoRoll) NoteOn

func (p *PianoRoll) NoteOn(note, velocity uint8)

NoteOn marks note as sounding from an external source (e.g. a real MIDI input device — see the graphite/midi package) and highlights its key. Safe to call for a note already sounding from another source; safe to call from any goroutine only via Application.Invoke, like any other widget mutation (see architecture.md's concurrency section) — a MIDI driver's callback runs on its own goroutine, not the render loop's.

type PlaybackMode

type PlaybackMode uint8

PlaybackMode defines how animation frames are played.

const (
	PlaybackStatic    PlaybackMode = 0
	PlaybackLoop      PlaybackMode = 1
	PlaybackBoomerang PlaybackMode = 2
	PlaybackOnce      PlaybackMode = 3
)

type ProgressBar

type ProgressBar struct {
	BaseWidget
	Label    string
	Progress float32
}

ProgressBar draws a labeled, filled bar showing completion from 0 to 100.

func NewProgressBar

func NewProgressBar(x, y, w int, label string) *ProgressBar

NewProgressBar creates a ProgressBar at (x, y) starting at 0% progress.

func (*ProgressBar) DrawRelative

func (pb *ProgressBar) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*ProgressBar) SetProgress

func (pb *ProgressBar) SetProgress(p float32)

SetProgress sets the completion percentage, clamped to [0, 100].

type RawInputReceiver

type RawInputReceiver interface {
	Widget
	WriteRaw(p []byte)
}

RawInputReceiver is implemented by a widget that needs terminal input as raw, undecoded bytes while it has focus — bypassing parseANSI's Event decoding entirely. Terminal (see terminal.go) is the only built-in widget that does: forwarding a shell's own keystrokes byte-for-byte is the whole point of an embedded terminal, and round-tripping them through graphite's own smaller KeyCode vocabulary first would lose anything that vocabulary doesn't happen to cover (application-cursor-mode arrows, exotic modifier combinations, ...).

type Slider

type Slider struct {
	BaseWidget

	Label    string
	Min      float64
	Max      float64
	Value    float64
	OnChange func(value float64)

	OnDoubleClick func()
	// contains filtered or unexported fields
}

Slider is a horizontal draggable control for setting a value within a range.

func NewSlider

func NewSlider(x, y, w int, label string, min, max float64) *Slider

NewSlider creates a Slider at (x, y) with the given width. Min and Max define the range of the slider. Value starts at Min.

func (*Slider) DrawRelative

func (s *Slider) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*Slider) HandleEvent

func (s *Slider) HandleEvent(ev Event)

HandleEvent implements Widget.

func (*Slider) SetValue

func (s *Slider) SetValue(v float64)

SetValue sets the slider's value, clamped to [Min, Max], and calls OnChange.

type Spinner

type Spinner struct {
	BaseWidget
	Label string
}

Spinner draws an animated braille-style busy indicator next to a label.

func NewSpinner

func NewSpinner(x, y int, label string) *Spinner

NewSpinner creates a Spinner at (x, y) sized to fit label.

func (*Spinner) DrawRelative

func (s *Spinner) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

type Tab

type Tab struct {
	Name    string
	Widgets []Widget
}

Tab is one page of a TabView: its label and the widgets shown while it is active.

type TabStyle

type TabStyle int

TabStyle selects how a TabView renders and whether it accepts focus/input.

const (
	// TabDefault is a focusable tab strip navigable with Left/Right and the
	// mouse.
	TabDefault TabStyle = iota
	// TabTimeline is a read-only progress indicator (e.g. wizard steps);
	// it never takes focus or input.
	TabTimeline
)

Supported tab-view styles.

type TabView

type TabView struct {
	BaseWidget
	Tabs   []Tab
	Active int
	Style  TabStyle
}

TabView switches between named pages of widgets, showing exactly one at a time.

func NewTabView

func NewTabView(x, y, w int, style TabStyle) *TabView

NewTabView creates an empty TabView at (x, y) with the given width and style.

func (*TabView) AddTab

func (tv *TabView) AddTab(name string, widgets []Widget)

AddTab appends a new page and refreshes widget visibility so only the active tab's widgets are shown.

func (*TabView) DrawRelative

func (tv *TabView) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*TabView) HandleEvent

func (tv *TabView) HandleEvent(ev Event)

HandleEvent implements Widget: Left/Right switch the active tab. No-op for TabTimeline, which is display-only.

func (*TabView) UpdateVisibility

func (tv *TabView) UpdateVisibility()

UpdateVisibility shows the active tab's widgets and hides every other tab's. Call this after changing Active directly.

type Terminal

type Terminal struct {
	BaseWidget

	// OnExit, if set, is called (from the main loop, via Application.Invoke
	// — never directly from readLoop's own goroutine) once the child
	// process exits, so a host program can close the tab/pane hosting it.
	OnExit func(err error)
	// contains filtered or unexported fields
}

Terminal is a widget that runs a shell (or any interactive program) attached to a real pseudo-terminal and renders its output faithfully — including full-screen programs like vim or htop, which need real cursor control and an alternate screen buffer, not just scrolling text.

Getting there needs two things working together: startPTY (pty.go and its per-OS files) gives the child process an actual controlling terminal, and vtScreen (vt100.go) interprets whatever escape sequences it emits into a screen grid. Terminal's own job is gluing those to graphite — rendering the grid via DrawRelative, and forwarding keystrokes back to the child completely undistorted: it implements RawInputReceiver (see app.go) so Application.Run routes it raw bytes straight from the real terminal in front of the user, bypassing graphite's own Event-decoding entirely. Round-tripping through graphite's smaller KeyCode vocabulary first would lose anything that vocabulary doesn't cover — application-cursor-mode arrows, exotic modifier combinations — which is exactly the "distortion" an embedded terminal can't afford.

Known gaps: no scrollback (only the visible grid), and DEC line-drawing character sets aren't translated, so a program that leans on them for box-drawing borders may show the raw designator characters instead.

func NewTerminal

func NewTerminal(app *Application, x, y, w, h int, shell string, args []string) (*Terminal, error)

NewTerminal spawns shell (with args) attached to a pseudo-terminal at (x, y) sized w×h — 0 or negative for either follows BaseWidget's usual "stretch to fill the parent" convention, exactly as any other widget's constructor does. The pty/screen themselves still need a real, positive starting size before the first layout pass ever runs, so they start at a sane placeholder (80×24) that DrawRelative immediately resizes to the widget's actually resolved size on the very first frame. app is used solely to deliver OnExit safely via Application.Invoke; Terminal does not otherwise reach into it.

func (*Terminal) Close

func (t *Terminal) Close() error

Close releases the pty's own resources. It does not wait for or kill the child process — Wait (via Exited, or the pty itself) does that.

func (*Terminal) DrawRelative

func (t *Terminal) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget: resizes the pty/screen to match this frame's resolved size before rendering, so the child sees an accurate terminal size (e.g. after the surrounding layout reflows), then paints every cell and, if the child left the cursor visible, a block cursor.

func (*Terminal) Exited

func (t *Terminal) Exited() (exited bool, err error)

Exited reports whether the child process has exited, and its result (nil on a clean exit) once it has.

func (*Terminal) HandleEvent

func (t *Terminal) HandleEvent(ev Event)

HandleEvent implements Widget. A mouse click just focuses the terminal (Window's own click-to-focus handling does the rest) — no key event ever reaches here while focused, since Application.Run routes those through WriteRaw instead once this widget has focus.

func (*Terminal) WriteRaw

func (t *Terminal) WriteRaw(p []byte)

WriteRaw implements RawInputReceiver: every byte is sent to the child exactly as received, with no interpretation.

type TextArea

type TextArea struct {
	BaseWidget
	Text      string
	Scroll    int
	CursorPos int // Absolute rune index into Text, not a line-local offset.
}

TextArea is a focusable, scrollable, multi-line text editor with word wrap and a visible cursor.

func NewTextArea

func NewTextArea(x, y, w, h int) *TextArea

NewTextArea creates an empty TextArea at (x, y) with the given size.

func (*TextArea) DrawRelative

func (ta *TextArea) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*TextArea) HandleEvent

func (ta *TextArea) HandleEvent(ev Event)

HandleEvent implements Widget: arrow keys move the cursor (Up/Down by visual line, preserving column where possible), Backspace/Delete/Enter edit the text, printable characters are inserted, and a mouse click moves the cursor to the clicked line and column.

func (*TextArea) SetText

func (ta *TextArea) SetText(text string)

SetText replaces the text and resets scroll and cursor to the start.

type TextLine

type TextLine struct {
	Start int
	Runes []rune
}

TextLine is one wrapped or newline-delimited line of a TextArea's text, with Start recording its offset (in runes) into the full text so cursor positions can be mapped back and forth between line-local and absolute coordinates.

type Theme

type Theme struct {
	BgScreen   Color
	BgWindow   Color
	FgWindow   Color
	BgWidget   Color
	BgFocused  Color
	FgFocused  Color
	Primary    Color
	Success    Color
	Danger     Color
	Warning    Color
	Disabled   Color
	FgDisabled Color
	// Accent is a secondary accent distinct from Primary, for a program
	// that needs to color two different things without one borrowing the
	// other's meaning — e.g. Primary for focus/selection and Accent for a
	// multi-selection tag marker. No built-in widget reads it; it exists
	// for custom widgets (see docs/custom-widgets.md) that need a second
	// accent of their own.
	Accent Color
	// Info is a third accent, distinct from both Primary and Accent, for a
	// transient "here's a result" highlight — e.g. a search match — that
	// would otherwise have to reuse a color already carrying a different
	// meaning (focus, a tag marker). No built-in widget reads it; like
	// Accent, it exists for a custom widget that needs its own.
	Info Color
}

Theme is the color palette a Canvas renders widgets with.

func DefaultTheme

func DefaultTheme() Theme

DefaultTheme returns the built-in color palette used by a new Canvas until Application.SetTheme overrides it: a GitHub-Dark-inspired scheme (near- black background, soft light-gray text, a blue accent, and green/red/amber semantic colors for success/danger/warning states).

type TodoItem

type TodoItem struct {
	Text  string
	State TodoState
}

TodoItem is one row of a TodoList: its label and current state.

type TodoList

type TodoList struct {
	BaseWidget
	Items    []TodoItem
	Selected int
	Scroll   int
	ReadOnly bool
}

TodoList is a scrollable checklist. In read-only mode it ignores input entirely and is meant to be driven programmatically via SetItemState (e.g. to reflect the progress of a background task).

func NewTodoList

func NewTodoList(x, y, w, h int, items []string, readOnly bool) *TodoList

NewTodoList creates a TodoList at (x, y) from the given item labels, all starting in TodoPending state.

func (*TodoList) DrawRelative

func (tl *TodoList) DrawRelative(c *Canvas, offX, offY, pW, pH int)

DrawRelative implements Widget.

func (*TodoList) HandleEvent

func (tl *TodoList) HandleEvent(ev Event)

HandleEvent implements Widget: Up/Down move the selection, Space/Enter and a mouse click on a row both toggle that row between TodoPending and TodoDone. No-op when ReadOnly.

func (*TodoList) SetItemState

func (tl *TodoList) SetItemState(idx int, state TodoState)

SetItemState sets the state of the item at idx, ignoring out-of-range indices so callers driving this from a background goroutine's progress loop don't need to bounds-check.

type TodoState

type TodoState int

TodoState is the completion state of a single TodoItem.

const (
	TodoPending TodoState = iota
	TodoRunning
	TodoDone
)

Supported todo-item states.

type Widget

type Widget interface {
	DrawRelative(c *Canvas, offX, offY, pW, pH int)
	DrawOverlay(c *Canvas, offX, offY, pW, pH int)
	HandleEvent(ev Event)
	HitTest(mx, my int) bool

	SetFocus(f bool)
	CanFocus() bool
	HasFocus() bool
	GetChildren() []Widget
	SetVisible(v bool)
	IsVisible() bool
	SetEnabled(e bool)
	IsEnabled() bool
	SetPosition(x, y int)
	SetPercentLayout(pctX, pctY, pctW, pctH int)
	GetFixedH() int
	GetFixedW() int
}

Widget is the contract every UI element implements to participate in layout, drawing, focus, and event routing. Custom widgets are built by embedding BaseWidget and overriding the methods that need non-default behavior.

type Window

type Window struct {
	FixedW, FixedH, PctW, PctH int
	Title                      string
	Children                   []Widget
	PaddingX, PaddingY         int
	Chrome                     WindowChrome
	// contains filtered or unexported fields
}

Window is a titled container holding a flat list of top-level children (which may themselves be containers like Panel) and owning focus navigation and event routing for the whole subtree. By default (ChromeBordered) it draws a border, drop shadow, and title, centered on the canvas at a fixed or percentage size — see ChromeBorderless for a full-screen alternative.

func NewFullscreenWindow

func NewFullscreenWindow() *Window

NewFullscreenWindow creates a Window with ChromeBorderless chrome and no padding — it fills the canvas exactly, edge to edge, with no border, shadow, or title bar. Call SetPercentLayout/SetPosition on individual children (or give the window PaddingX/PaddingY) for breathing room; the window itself adds none by default, unlike NewWindow's 4/2.

func NewWindow

func NewWindow(w, h int, title string) *Window

NewWindow creates a Window with a fixed size and title, and default padding around its content area.

func (*Window) AddWidget

func (w *Window) AddWidget(widget Widget)

AddWidget appends widget as a top-level child. If no widget in the window currently has focus, the first focusable widget (including one nested inside widget, if it is a container) becomes focused.

func (*Window) ClearMouseCapture

func (w *Window) ClearMouseCapture()

ClearMouseCapture forcefully releases any active mouse capture in this window, preventing mouse up/drag events from being routed to the widget that triggered a modal open.

func (*Window) Draw

func (w *Window) Draw(c *Canvas)

Draw renders the window, then its children within the resulting padded content area. With ChromeBorderless (see NewFullscreenWindow), that's the whole canvas with no decoration; the default ChromeBordered instead draws a frame, drop shadow, and title centered on c at a fixed or percentage size.

func (*Window) HandleEvent

func (w *Window) HandleEvent(ev Event)

HandleEvent routes a mouse or keyboard event to the appropriate widget: a mouse press goes to the deepest widget hit-tested under the pointer and captures the mouse, so the resulting drag/release events go straight to that same widget regardless of where the pointer moves next; a scroll or right-click event is hit-tested and delivered the same way but without moving focus or starting a capture, since neither has a drag/release to capture for; Tab advances focus through the flattened focus order; all other key events go to whichever widget currently has focus. Disabled widgets never receive an event, regardless of what their own HandleEvent does.

func (*Window) HasMouseCapture

func (w *Window) HasMouseCapture() bool

HasMouseCapture reports whether a mouse gesture is still in flight — EventMouseDown has hit some widget but the matching EventMouseUp hasn't arrived yet. Application.Run checks this before switching a focused RawInputReceiver's raw-passthrough on: if a gesture that started before focus changed (e.g. a click on a menu item that itself creates and focuses a new Terminal tab) is still open, its trailing EventMouseUp needs to reach the widget that captured it — normally decoded and routed — rather than being redirected to the newly focused widget as raw bytes.

func (*Window) SetPercentSize

func (w *Window) SetPercentSize(pw, ph int)

SetPercentSize switches the window to a size relative to the canvas instead of FixedW/FixedH.

type WindowChrome

type WindowChrome int

WindowChrome selects how much decoration Window.Draw paints around a window's content.

const (
	// ChromeBordered draws a titled border and drop shadow, centered on
	// the canvas at the window's fixed/percentage size. Window's original
	// behavior, and still the default (the zero value), so every existing
	// window is completely unaffected by ChromeBorderless's addition.
	ChromeBordered WindowChrome = iota
	// ChromeBorderless fills the entire canvas with no border, shadow, or
	// title bar — for a full-screen application's main window (e.g. a
	// commander-style file manager) rather than a floating dialog.
	// FixedW/FixedH/PctW/PctH are ignored in this mode: the window is
	// always exactly the canvas size.
	ChromeBorderless
)

Supported chrome modes.

Directories

Path Synopsis
Command showcase exercises every widget Graphite ships, its Flex layout container, and a custom theme, in a single tabbed window.
Command showcase exercises every widget Graphite ships, its Flex layout container, and a custom theme, in a single tabbed window.

Jump to

Keyboard shortcuts

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