minigui

package module
v0.1.9 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 14 Imported by: 0

README

minigui

A tiny immediate-mode GUI toolkit for Ebitengine, meant to be shared across small Ebiten programs that need basic user input without pulling in a heavy widget framework. Its dependencies are Ebitengine itself and native (cgo-free system clipboard for copy/cut/paste); everything else is the Go standard library.

Status: early but usable — pluggable text faces (see Fonts) and per-app styling (see Theming).

Model

Immediate mode: every frame you build an Input, call Begin, run the widgets, then End; widgets read the input and append draw commands that Render flushes to an *ebiten.Image. Input is a plain struct, so the widget logic is testable without a window.

type game struct {
	gui   minigui.Context
	name  string
	count int
}

func (g *game) Update() error {
	g.gui.Begin(minigui.InputFromEbiten(), 20, 20)
	g.gui.Label("hello")
	if g.gui.Button("inc", fmt.Sprintf("count: %d", g.count)) {
		g.count++
	}
	g.gui.TextField("name", &g.name)
	g.gui.End()
	return nil
}

func (g *game) Draw(screen *ebiten.Image) {
	g.gui.Render(screen)
}

Widgets

  • Label(text) — static text.
  • Button(id, label) bool — reports a click this frame.
  • Toggle(id, label, on) bool — button with an active state.
  • Swatch(id, color, selected) bool — clickable color square.
  • TextField(id, *string) bool — editable single-line field (focus, caret, horizontal scroll); reports whether the text changed.
  • TextArea(id, *string, rows) bool — multi-line editor (Enter inserts a line, Up/Down move between lines, line-relative Home/End, vertical scroll).
  • List(id, items, *selected) bool / ListWithIcons(...) — scrollable, selectable list; the icon variant reserves a square per row for the caller to draw into.

Text editing (TextField and TextArea) comes with selection (Shift+arrows, mouse drag, double-click word select, Ctrl/Cmd+A), system-clipboard copy/cut/paste (Ctrl/Cmd+C/X/V, via native/clipboard), and undo (Ctrl/Cmd+Z, with typing runs coalesced).

Layout helpers: SameLine, SetItemWidth. Focus helpers: HasFocus, ClearFocus (so the host can suspend single-key shortcuts while a field is being typed into), Focus(id) to hand focus to a widget, and Submitted(id) to detect Enter in a field.

Fonts

By default a Context renders labels with Ebitengine's built-in debug font (a fixed 6×16 cell, ASCII only). Call SetFace to use any text/v2 face — then text is measured and drawn through it (so widths, the caret and row heights follow the font), non-Latin scripts such as Japanese render, and label colors are honored.

SystemFace(size) is a convenience that loads a CJK-capable operating-system font (it tries a small per-OS list of well-known paths, no font bundled, no external dependency) and returns a face for SetFace. It returns an error when none is found, so you can fall back to the debug font:

if face, err := minigui.SystemFace(16); err == nil {
	gui.SetFace(face)
}

To bundle your own font instead, build a face from embedded bytes with text.NewGoTextFaceSource and pass it to SetFace.

Theming

Colors and layout metrics live in a Style. A zero-value Context uses DefaultStyle (a dark, cyan-tinted scheme); override what you want and apply it with SetStyle:

s := minigui.DefaultStyle()
s.Button = color.RGBA{0x20, 0x14, 0x28, 0xff}
s.Focus = color.RGBA{0xff, 0x99, 0x33, 0xff}
s.RowH = 28
s.Face = face // optional; same as SetFace
gui.SetStyle(s)

SetFace is a shortcut that sets only Style.Face; Style() returns the current style for reading or tweaking a single field.

VGAPalette is the classic 16-color IBM VGA text palette as hex strings — handy as a ready-made swatch set for Swatch rows in retro-styled tools.

Panels

BeginPanel(title, x, y) / EndPanel() frame the widgets between them in a titled background box (a small simulated window), auto-sized to the content. EndPanel returns the panel's screen rectangle, handy for hit-testing the whole panel — e.g. to keep it interactive in a click-through overlay.

gui.Begin(in, 0, 0)
gui.BeginPanel("Tools", 8, 8)
gui.Toggle("draw", "Draw", drawing)
rect := gui.EndPanel()
gui.End()

Windows

A Window is a panel with a life of its own: draggable by the title bar and closable by a close box. The caller owns the state (X, Y, Open persist across frames); minigui moves X/Y during a drag and clears Open on close (NoClose hides the close box, e.g. for an always-present toolbar). Dragging() reports an in-progress drag, so a click-through host can keep grabbing the mouse until the drag ends.

var win = minigui.Window{Title: "Tools", X: 8, Y: 8, Open: true}

if gui.BeginWindow(&win) {
	gui.Toggle("draw", "Draw", drawing)
	gui.EndWindow()
}

Install

go get github.com/crgimenes/minigui

Demo

go run github.com/crgimenes/minigui/cmd/minigui-demo

License

See LICENSE.


More of my projects

  • native: cgo-free Go bindings for OS APIs: clipboard, mmap, keep-awake, and friends.
  • NeoFrame: draw over your screen; a transparent overlay for demos and classes.
  • kutta: a 2D wind tunnel; watch air misbehave around an airfoil.
  • glaze: WebView desktop apps in Go, cgo-free.

More at github.com/crgimenes and crg.eti.br.

Documentation

Overview

Package minigui is a tiny immediate-mode GUI toolkit for Ebitengine. Each frame the caller builds an Input, runs Begin, calls widgets, then End; widgets process input and append draw commands that Render flushes. Keeping the logic separate from rendering makes the toolkit testable without a window and keeps a consistent look across the host application.

Index

Constants

This section is empty.

Variables

View Source
var VGAPalette = []string{
	"#000000", "#0000aa", "#00aa00", "#00aaaa",
	"#aa0000", "#aa00aa", "#aa5500", "#aaaaaa",
	"#555555", "#5555ff", "#55ff55", "#55ffff",
	"#ff5555", "#ff55ff", "#ffff55", "#ffffff",
}

VGAPalette is the classic 16-color IBM VGA text palette, as hex strings. It is offered as a convenient default for the color pickers callers build with Swatch; the palette lives here so there is one source of truth for it.

Functions

This section is empty.

Types

type Context

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

Context holds immediate-mode state that persists across frames (focus, caret) plus this frame's input, layout cursor and draw-command buffer.

func (*Context) Begin

func (c *Context) Begin(in Input, x, y float64)

Begin starts a frame, laying widgets out from the given top-left position.

func (*Context) BeginPanel

func (c *Context) BeginPanel(title string, x, y float64)

BeginPanel starts a titled panel at (x, y): a fixed background box with a title bar that frames the widgets drawn until EndPanel, like a small simulated window. The box is sized automatically to its content. Panels do not nest. For a movable, closable window backed by caller state, use BeginWindow.

func (*Context) BeginWindow

func (c *Context) BeginWindow(w *Window) bool

BeginWindow starts the window w. It returns false when w is closed (w.Open is false): skip its contents and do not call EndWindow. When it returns true, draw widgets and call EndWindow.

func (*Context) Button

func (c *Context) Button(id ID, label string) bool

Button draws a clickable button and reports whether it was clicked this frame.

func (*Context) ClearFocus

func (c *Context) ClearFocus()

ClearFocus drops keyboard focus, e.g. when the panel owning the field is hidden.

func (*Context) Dragging

func (c *Context) Dragging() bool

Dragging reports whether a window is currently being dragged, so a click-through host can keep grabbing the mouse until the drag ends, even if the cursor briefly outruns the title bar.

func (*Context) End

func (c *Context) End()

End finishes the frame, clearing focus when the click missed every field.

func (*Context) EndPanel

func (c *Context) EndPanel() image.Rectangle

EndPanel finishes the panel and returns its screen rectangle, useful for hit-testing the whole panel (e.g. to keep it interactive in a click-through overlay).

func (*Context) EndWindow

func (c *Context) EndWindow() image.Rectangle

EndWindow finishes the current window: it draws the close box, handles dragging the title bar and clicking the close box (mutating the caller's Window), and returns the window's screen rectangle.

func (*Context) Focus

func (c *Context) Focus(id ID)

Focus gives keyboard focus to the field with the given id, e.g. to focus a text field as soon as its window opens, with the caret at the start of the text. It marks the click as handled so End does not clear the focus on the same frame as the click that triggered the focusing.

func (*Context) HasFocus

func (c *Context) HasFocus() bool

HasFocus reports whether a text field owns keyboard input, so the caller can suspend single-key shortcuts while the user types.

func (*Context) Label

func (c *Context) Label(s string)

Label draws a line of static text.

func (*Context) List

func (c *Context) List(id ID, items []string, selected *int) bool

List draws a scrollable, selectable list bound to *selected (the chosen index) and reports whether the selection changed this frame. Click an item to select it; scroll the wheel while hovering to move through a longer list.

func (*Context) ListWithIcons

func (c *Context) ListWithIcons(id ID, items []string, selected *int, iconPx float64) (changed bool, clicked int, icons []IconSlot)

ListWithIcons behaves like List but reserves a square icon column of iconPx on the left of each row, insetting the text. It also returns the index clicked this frame (-1 if none, even when it equals the current selection, so callers can detect double-clicks) and the icon rect of each visible row so the caller can draw into it after Render; the slices/values are valid until the next frame.

func (*Context) Render

func (c *Context) Render(dst *ebiten.Image)

Render flushes the frame's draw commands into dst.

func (*Context) SameLine

func (c *Context) SameLine()

SameLine places the next widget to the right of the one just drawn, on the same row, instead of on a new line below it.

func (*Context) SetFace

func (c *Context) SetFace(f Face)

SetFace sets the text face used to measure and draw widget labels, leaving the rest of the style untouched. Pass nil to fall back to Ebitengine's built-in debug font (a fixed 6x16 cell, ASCII only). A real face also lets non-Latin text (e.g. Japanese) render. It persists across frames.

func (*Context) SetItemWidth

func (c *Context) SetItemWidth(w float64)

SetItemWidth fixes the width of subsequent buttons and toggles so a column of them can share one size (0 sizes each to its text). Reset every frame by Begin.

func (*Context) SetStyle

func (c *Context) SetStyle(s Style)

SetStyle sets the colors, metrics and face used to draw widgets. Start from DefaultStyle and override what you need. It persists across frames.

func (*Context) Slider added in v0.1.6

func (c *Context) Slider(id ID, val *float64, lo, hi float64) bool

Slider draws a horizontal draggable track that edits *val within [lo, hi], reporting whether the value changed this frame. Clicking anywhere on the row jumps the value to the cursor and starts a drag that keeps following the mouse until the button is released, even if the cursor leaves the row. The width is the style's FieldW; a label and a formatted value are composed by the caller with Label and SameLine, like the other widgets.

func (*Context) Style

func (c *Context) Style() Style

Style returns the Context's current style, initializing it to DefaultStyle if it has not been set, so callers can read or tweak individual fields.

func (*Context) Submitted

func (c *Context) Submitted(id ID) bool

Submitted reports whether Enter was pressed this frame while the field with the given id had keyboard focus — handy to run a command typed into a TextField. Call it after the field so focus reflects this frame's clicks.

func (*Context) Swatch

func (c *Context) Swatch(id ID, col color.RGBA, selected bool) bool

Swatch draws a clickable color square, highlighted when selected, and reports whether it was clicked this frame.

func (*Context) TextArea

func (c *Context) TextArea(id ID, s *string, rows int) bool

TextArea draws an editable multi-line field bound to *s, showing rows lines, and reports whether the text changed this frame. It supports the same selection and clipboard shortcuts as TextField, plus Enter for a newline and Up/Down to move between lines; long lines scroll horizontally (no wrap).

func (*Context) TextField

func (c *Context) TextField(id ID, s *string) bool

TextField draws an editable single-line field bound to *s and reports whether the text changed this frame. Clicking it gives it focus; while focused it edits the text, supports a selection (Shift + arrows, Ctrl/Cmd+A) and clipboard copy/cut/paste (Ctrl/Cmd+C/X/V) via the system clipboard.

func (*Context) Toggle

func (c *Context) Toggle(id ID, label string, on bool) bool

Toggle is a button that shows an active state, used for tools and on/off options; it reports whether it was clicked this frame.

type Face

type Face = text.Face

Face is the text face used to render widget labels. It is an alias for Ebitengine's text/v2 Face, so a caller can supply any font: a system font (see SystemFace), an embedded TTF, or a bitmap face. When a Context has no face it falls back to Ebitengine's built-in debug font.

func SystemFace

func SystemFace(size float64) (Face, error)

SystemFace loads a text face from a well-known operating-system font at the given size in pixels, ready to pass to (*Context).SetFace. It tries a small, per-OS list of font paths (CJK-capable ones first, so Japanese and other non-Latin text render) and returns the first that loads. When none load it returns an error, so the caller can fall back to the built-in debug font by leaving the Context's face unset.

It depends only on the standard library and Ebitengine; it does no font-config discovery, so the lists are best-effort and can miss unusual installations.

type ID

type ID string

ID identifies a widget across frames, used to track which one holds focus.

type IconSlot

type IconSlot struct {
	Index int
	X, Y  float64
	Size  float64
}

IconSlot is the screen-space square reserved for one visible row's icon, returned by ListWithIcons. The toolkit only draws fills, borders and text, so the caller renders the icon (e.g. an asset thumbnail) into this rect itself.

type Input

type Input struct {
	MouseX, MouseY float64
	MouseDown      bool    // left button held
	MouseClicked   bool    // left button pressed this frame
	WheelY         float64 // vertical scroll this frame (ebiten wheel dy)
	Chars          []rune
	Backspace      bool
	Left, Right    bool
	Up, Down       bool
	Home, End      bool
	Enter          bool
	Shift          bool // shift held, to extend the selection with Left/Right/Home/End
	Copy           bool // Ctrl/Cmd+C this frame
	Cut            bool // Ctrl/Cmd+X this frame
	Paste          bool // Ctrl/Cmd+V this frame
	SelectAll      bool // Ctrl/Cmd+A this frame
	Undo           bool // Ctrl/Cmd+Z this frame
}

Input is the per-frame input snapshot. It is plain data so the toolkit can be driven from tests; InputFromEbiten builds it from the live ebiten state.

func InputFromEbiten

func InputFromEbiten() Input

InputFromEbiten samples the live ebiten input into an Input snapshot.

type Style

type Style struct {
	Face Face // text face; nil uses the built-in debug font

	Text      color.RGBA // label and field text
	Border    color.RGBA // widget outline
	Button    color.RGBA // button/toggle background
	ButtonHot color.RGBA // hovered button background
	ButtonOn  color.RGBA // active toggle background
	Field     color.RGBA // text field and list background
	Focus     color.RGBA // focused outline, caret and selection accent
	Selection color.RGBA // background of selected text

	RowH   float64 // height of a standard widget row
	Pad    float64 // inner padding
	Gap    float64 // gap between adjacent widgets
	FieldW float64 // width of text fields and lists
}

Style holds the look of a Context: the text face, the widget colors and the layout metrics. Start from DefaultStyle, override the fields you want, then apply it with (*Context).SetStyle, so a host application can give the toolkit its own palette and sizing.

func DefaultStyle

func DefaultStyle() Style

DefaultStyle returns the toolkit's built-in look: a dark, cyan-tinted scheme with the v1 metrics. A zero-value Context uses it until SetStyle is called.

type Window

type Window struct {
	Title   string
	X, Y    float64
	Open    bool
	NoClose bool // hide the close box, e.g. for an always-present toolbar
}

Window is the persistent state of a draggable, closable window. The caller owns it: X/Y and Open survive across frames. minigui moves X/Y while the title bar is dragged and clears Open when the close box is clicked (unless NoClose is set).

Directories

Path Synopsis
cmd
minigui-demo command
Command minigui-demo is a sandbox for the minigui package: a panel with a label, a button and an editable text field, so the immediate-mode toolkit can be exercised on its own.
Command minigui-demo is a sandbox for the minigui package: a panel with a label, a button and an editable text field, so the immediate-mode toolkit can be exercised on its own.

Jump to

Keyboard shortcuts

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