ui

package
v0.2.1 Latest Latest
Warning

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

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

Documentation

Overview

Package ui is the reusable terminal view layer for TwixT: a board renderer with two drawing scales and a scrolling viewport, a layout engine that fits board and information panel into any terminal size, and a data-driven keymap. Game screens are built on top of it; the package itself owns no game flow beyond a demonstration model.

Index

Constants

View Source
const (
	MinWidth  = 20
	MinHeight = 6
)

Minimum terminal size for showing a board. Below either bound the frame is an explicit too-small notice instead: the smallest useful board view needs the row-number gutter plus a handful of hole columns, and the letters row plus a few board rows plus the status line.

View Source
const JumpStep = 3

JumpStep is how many holes the shifted movement keys jump.

Variables

View Source
var (
	Compact = Scale{/* contains filtered or unexported fields */}
	Detail  = Scale{/* contains filtered or unexported fields */}
)

The two supported scales. Compact fits a standard 24×24 board into roughly 52×26 cells; Detail spreads it over roughly 98×49 and draws every link with three-cell strokes.

Functions

func Compose

func Compose(arr Arrangement, board, panel []string, status string, st *Styles) string

Compose assembles the final frame from the rendered board, the panel lines and the status line, clipped so that no line exceeds the arrangement's width and no more than its height lines are emitted. Board lines must come from BoardView.Render with the arrangement's board bounds.

Types

type Action

type Action uint8

Action is something the board UI can do in response to a key.

const (
	ActNone Action = iota
	ActMoveLeft
	ActMoveRight
	ActMoveUp
	ActMoveDown
	ActJumpLeft
	ActJumpRight
	ActJumpUp
	ActJumpDown
	ActEdgeTop
	ActEdgeBottom
	ActEdgeLeft
	ActEdgeRight
	ActPlacePeg
	ActConfirm
	ActLinkMode
	ActToggleLink
	ActAbortTurn
	ActExitMode
	ActQuit
)

Actions. Movement actions apply in every context; link actions only in link mode. ActConfirm is context-sensitive: it places the peg when none is staged and commits the turn otherwise.

type Arrangement

type Arrangement struct {
	Width, Height int
	TooSmall      bool
	Scale         Scale

	// BoardAvailW and BoardAvailH bound what BoardView.Render may use.
	BoardAvailW, BoardAvailH int
	// BoardW and BoardH are the actual block size after clipping to the
	// available space, used to place the panel without wasted columns.
	BoardW, BoardH int

	Panel          PanelPlacement
	PanelW, PanelH int
}

Arrangement is the layout decision for one terminal size: which scale to draw the board at, how much space the board gets, and where the information panel sits. A status line is always reserved at the bottom except in the too-small state.

func Arrange

func Arrange(width, height, n int) Arrangement

Arrange decides the layout for a terminal of width×height showing an n-hole board. The rules, in order: the detail scale is used when its full board fits; otherwise the compact scale, with a viewport when even that does not fit. The panel goes beside the board when there is width for it, else below when there is height, else information lives in the status line alone.

type Binding

type Binding struct {
	Action   Action
	Keys     []string
	Contexts Context
	// Label is the short key name shown in help, Help the one-line meaning,
	// Short an optional terse verb for one-line hint strings.
	Label string
	Help  string
	Short string
}

Binding maps keys to an action. Keys are Bubble Tea key strings as returned by tea.KeyPressMsg.String(). Every key here is reliable inside a terminal multiplexer: unmodified printables, uppercase letters, and the basic special keys — no modified arrows, no protocol-dependent combinations.

type BoardView

type BoardView struct {
	// Scale selects the drawing density. The layout engine picks it per frame.
	Scale Scale
	// Cursor is the hole the cursor sits on; ShowCursor turns it on. It is
	// drawn as square brackets either side of the hole, or, where a link owns
	// those cells, as a mark on the hole itself.
	Cursor     game.Point
	ShowCursor bool
	// LastMove marks the peg just played, so it can be found on a large board
	// without reading the coordinate off the panel. ShowLastMove turns it on.
	// An overlay on the same hole takes the cell, so the ring is hidden while
	// the cursor rests on the last move, which is the one time it is not
	// needed.
	LastMove     game.Point
	ShowLastMove bool
	// Highlights marks holes to call out (hints, tutorial steps, the staged
	// peg). They render as round brackets around the hole, falling back to a
	// mark on the hole itself the same way the cursor does.
	Highlights []game.Point
	// Digits overlays link-direction digits on target holes in link mode.
	Digits map[game.Point]rune
	// contains filtered or unexported fields
}

BoardView renders a game onto a scrolling viewport. The zero value is usable; set Scale before rendering. Viewport position is internal state that follows the cursor and survives resizes.

func (*BoardView) Render

func (bv *BoardView) Render(g *game.Game, st *Styles, availW, availH int) []string

Render draws the game into at most availW×availH cells, including the coordinate labels, scrolling as needed to keep the cursor visible. Lines are styled with st and right-trimmed; every line's display width is at most availW and at most availH lines are returned.

func (*BoardView) Viewport

func (bv *BoardView) Viewport() (top, left int)

Viewport returns the current viewport origin in canvas cells, for tests and debugging.

type Context

type Context uint8

Context is a keymap context. Contexts form a bitmask so one binding can serve several.

const (
	CtxBoard Context = 1 << iota
	CtxLink
)

The two contexts: normal board navigation, and link mode where the digit keys toggle links.

type Demo

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

Demo is a Bubble Tea model exercising the whole view layer: a hotseat sandbox where both sides are played from the keyboard. Game screens are built elsewhere; this model exists to prove the renderer, layout, viewport and keymap against a live engine, and to serve as the wiring example.

func NewDemo

func NewDemo(rs game.Ruleset) (*Demo, error)

NewDemo returns a demo model for the given ruleset. Colour is dropped when NO_COLOR is set, per the convention.

func (*Demo) Init

func (d *Demo) Init() tea.Cmd

Init implements tea.Model. The initial window size arrives as a message.

func (*Demo) Update

func (d *Demo) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update implements tea.Model.

func (*Demo) View

func (d *Demo) View() tea.View

View implements tea.Model.

type HelpEntry

type HelpEntry struct {
	Label string
	Help  string
}

HelpEntry is one help line: a key label and what it does.

type Keymap

type Keymap []Binding

Keymap is an ordered list of bindings: one source of truth for dispatch, help text and documentation.

func DefaultKeymap

func DefaultKeymap() Keymap

DefaultKeymap returns the standard bindings.

func (Keymap) ByAction

func (km Keymap) ByAction(ctx Context, a Action) (Binding, bool)

ByAction finds the binding for an action in a context.

func (Keymap) HelpEntries

func (km Keymap) HelpEntries(ctx Context) []HelpEntry

HelpEntries returns the help rows for a context, in keymap order.

func (Keymap) HelpTable

func (km Keymap) HelpTable(ctx Context) string

HelpTable renders the whole keymap as aligned plain-text rows, one binding per line, for documentation and the tutorial.

func (Keymap) HintLine

func (km Keymap) HintLine(ctx Context, actions ...Action) string

HintLine renders a terse status hint for the given actions, with key labels taken from the bindings so hint text can never drift from the real map.

func (Keymap) Lookup

func (km Keymap) Lookup(ctx Context, key string) (Binding, bool)

Lookup resolves a key in a context. The pressed key is also returned to the caller through the binding's Keys, so multi-key bindings such as the link digits can recover which key fired.

type PanelPlacement

type PanelPlacement uint8

PanelPlacement says where the information panel goes.

const (
	PanelNone PanelPlacement = iota
	PanelSide
	PanelBottom
)

Panel placements: none (status line only), beside the board, below it.

type Scale

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

Scale is a board drawing density: how many screen columns and rows separate neighbouring holes.

The knight-move link geometry constrains the choice hard. With colStep=2c and rowStep=c, a steep link (column ±1, row ±2) spans 2c×2c screen cells — an exact diagonal — and a shallow link (column ±2, row ±1) runs at slope 1/4, which the renderer draws as a ramp of horizontal scan-line glyphs. Both provided scales keep that shape, so one rasteriser serves both, and both match the roughly 1:2 width:height aspect of terminal cells, so the board looks square.

func ScaleFor added in v0.1.1

func ScaleFor(width, height, n int) Scale

ScaleFor picks the drawing scale for an n-hole board from the space the board itself is going to get: the detail scale when its whole block fits there, the compact one otherwise, which a viewport then clips if even that does not fit.

The space to pass is the board's share, not the terminal's. A caller that takes rows away from the board before drawing — the tutorial gives most of the screen to its prose — and asks about the terminal instead will choose detail for a board that then has to be clipped, when compact would have shown the whole of it.

func (Scale) BlockSize

func (sc Scale) BlockSize(n int) (w, h int)

BlockSize returns the full size of the rendered board block — canvas plus gutter and letters row — before any viewport clipping.

func (Scale) CanvasSize

func (sc Scale) CanvasSize(n int) (w, h int)

CanvasSize returns the size of the unclipped board canvas for an n-hole side, excluding coordinate labels. One margin column on each side leaves room for cursor brackets around edge holes.

func (Scale) String

func (sc Scale) String() string

String returns the scale's name.

type Styles

type Styles struct {
	// Plain suppresses all styling, leaving pure text.
	Plain bool

	Hole           lipgloss.Style
	PegVertical    lipgloss.Style
	PegHorizontal  lipgloss.Style
	LinkVertical   lipgloss.Style
	LinkHorizontal lipgloss.Style
	Cursor         lipgloss.Style
	Highlight      lipgloss.Style
	LinkDigit      lipgloss.Style
	LastMove       lipgloss.Style
	Label          lipgloss.Style

	PanelTitle lipgloss.Style
	PanelText  lipgloss.Style
	Status     lipgloss.Style
	Message    lipgloss.Style
	TooSmall   lipgloss.Style
}

Styles holds every style the view layer uses. The zero value styles nothing; use DefaultStyles for the coloured set and PlainStyles when colour must be off (NO_COLOR). Rendering never depends on these for meaning — they are reinforcement over an already unambiguous glyph set.

func DefaultStyles

func DefaultStyles() Styles

DefaultStyles returns the coloured style set. Colours come from the ANSI-16 palette so they follow the user's terminal theme, and the colour profile machinery in Bubble Tea degrades them further when the terminal is limited.

func PlainStyles

func PlainStyles() Styles

PlainStyles returns a style set that applies nothing, for NO_COLOR and for tests that assert on raw glyphs.

func StylesFor

func StylesFor(t theme.Theme) Styles

StylesFor maps a colour scheme onto the style set the view layer uses.

A scheme with no colours, which is both the monochrome theme and what a terminal reporting no colour support gets, produces the plain style set rather than a set of empty styles: rendering then takes the same path as a test asserting on raw glyphs, so there is one behaviour to reason about instead of two that only look alike.

Jump to

Keyboard shortcuts

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