package widget

import "git.teletypegames.org/engines/inkwell/inkbase/widget"

Package widget is the widget contract: the Widget interface, the Layer constants, and the visibility and ordering helpers a host drives its per-frame loop with.

A Widget is inkbase.Named and has Tick and Draw, both handed a UICtx carrying the frame's DT and the Game as an opaque any, which the widget type-asserts to the host interface it needs. A widget is responsible for its own input, hit testing, state and drawing; there is no layout layer — widgets carry their own bounds or work them out.

Optional interfaces

Hosts

The Game in a UICtx is whatever the host loop put there. A widget that needs more than DT type-asserts it to an interface of its own; the widget packages of inkbase each call theirs Host, and most embed Screen — the screen size and text drawing. A Game that does not implement a widget's Host leaves the widget inert rather than panicking. inkwell's *Game implements every one of them.

Z-order and input

Draw runs from the bottom layer up (OrderedWidgets), so a widget on a higher layer is painted on top; inside one layer, registration order decides. Tick runs from the top layer down (ReversedWidgets), so the widget drawn on top gets the click first. A widget claims a click or a key by consuming it on the host's input (see the input package), and the widgets below see it as gone.

A widget declares its layer rather than inheriting one from the order it was registered in, which is what lets a game register its widgets one file at a time, in whatever order the files fall, without the HUD coming out shuffled. A wrapper that forwards to an inner widget should return LayerOf(inner) from its own Layer, so that wrapping does not move it.

A running script normally has the frame to itself: no widget ticks until it is done, which keeps a click from racing a cutscene. An Awake widget opts out and ticks on those frames too, before the script's runner, so a click or key it consumes is not also read as "skip this line". Two inkwell built-ins need it, for the same reason: a menu, which must stay closable while a cutscene plays, and the dialog box, because a conversation is the running script and its lines would never advance otherwise. Nothing else should.

Custom widgets

Anything that implements Widget plugs in; the host cares about nothing beyond the name, and calls Tick and Draw:

type Clock struct{ Name string }

func (c *Clock) GetName() string             { return c.Name }
func (c *Clock) Layer() widget.Layer         { return widget.LayerHUD }
func (c *Clock) Tick(ctx *widget.UICtx)      {}
func (c *Clock) Draw(dst *ebiten.Image, ctx *widget.UICtx) {
	host := ctx.Game.(interface{ StateVar(string) any })
	font.DrawText(dst, fmt.Sprint(host.StateVar("time")), 4, 4, color.White)
}

Index

Functions

func WidgetAwake

func WidgetAwake(w Widget) bool

WidgetAwake reports whether a widget ticks during a running script.

func WidgetVisible

func WidgetVisible(ctx *inkbase.Ctx, w Widget) bool

WidgetVisible reports whether a widget takes part in this frame at all.

Types

type Awake

type Awake interface {
	TicksDuringScript() bool
}

Awake is implemented by a widget that ticks even during a running script.

type ClickBlocker

type ClickBlocker interface {
	BlocksClickAt(ctx *UICtx, p inkbase.Point) bool
}

ClickBlocker is the optional widget contract for "the screen under me is mine": a visible widget that reports p as blocked keeps a click there from reaching whatever the host puts behind its widgets — in inkwell the scene, with its hotspots and its walk — and keeps a widget that pops up where the pointer is (a verb coin) from opening over it. The widget's own Tick still gets the click first.

type Gated

type Gated interface {
	VisibleWhen() inkbase.Condition
}

Gated is implemented by a widget that can be switched off by game state.

type Layer

type Layer int

Layer is a widget's place in the paint order.

const (
	LayerScene   Layer = 0   // the scene itself and what stands in it
	LayerPanel   Layer = 100 // panels docked around the scene
	LayerHUD     Layer = 200 // heads-up elements: inventory, hover labels
	LayerSpeech  Layer = 300 // speech bubbles
	LayerDialog  Layer = 400 // dialog choices
	LayerMenu    Layer = 500 // menus over the game
	LayerCurtain Layer = 600 // full-screen fades and flashes
	LayerCursor  Layer = 700 // the mouse cursor, above everything
)

The standard layers, from the back to the front. A higher Layer is drawn later, on top; the gaps leave room for layers in between.

func LayerOf
func LayerOf(w Widget) Layer

LayerOf reports the layer a widget paints on.

type Layered

type Layered interface {
	Layer() Layer
}

Layered is implemented by a widget that declares its own layer.

type Screen

type Screen interface {
	// ScreenSize is the logical screen's width and height in pixels.
	ScreenSize() (width, height int)
	// DrawText draws s in colour c with its top-left corner at x, y.
	DrawText(dst *ebiten.Image, s string, x, y int, c color.Color)
}

Screen is the drawing surface most widgets need from their host: the logical screen size and text drawing in the host's font. The widget packages embed it in their own Host interfaces.

type UICtx

type UICtx struct {
	DT   float64
	Game any
}

UICtx is the per-tick context handed to widgets. Game is opaque; widgets type-assert it to their own host interface.

type Widget

type Widget interface {
	inkbase.Named
	Tick(ctx *UICtx)
	Draw(dst *ebiten.Image, ctx *UICtx)
}

Widget is the minimal surface a HUD element must implement.

func OrderedWidgets
func OrderedWidgets(ws []Widget) []Widget

OrderedWidgets sorts widgets bottom-to-top by layer for drawing.

func ReversedWidgets
func ReversedWidgets(ws []Widget) []Widget

ReversedWidgets returns top-to-bottom order for ticking (input).