package menu
import "git.teletypegames.org/engines/inkwell/inkbase/menu"
Package menu is the menu system: Menu, a nested list in a window — a page of rows, any of which can open a page of its own, as deep as the tree goes. It is a widget like any other, on the menu layer, gated by When, and it is the whole of the menu system: a debug menu, a pause menu, a main menu and an in-fiction device screen are this one widget with a different Root. The root inkwell package re-exports it — its *Game is the menu's Host — and builds the debug menu and the explorer on it.
Rows and pages
A row and a page are the same type, MenuItem. Its Label names the row on its parent's page and titles the page it opens, which is what makes the breadcrumb in the title bar fall out for free. A row opens one of three kinds of page:
- Items: a page as an author typed it. It costs nothing per frame.
- Expand: a page read out of the game. It is rebuilt while it is open, so a list of what is clickable in the current scene rewrites itself when the player leaves the scene.
- Body: a paragraph rather than rows — the Text wrapped to the window in the player's language and scrolled with the same keys, with nothing to pick. A codex entry, a letter, a bio.
A row with none of the three is a leaf, and Enter hands its Do to the host. Value is the right-hand column — a count, a target, "held" — and FixedValue makes one that does not depend on the game. Enabled false dims the row (darkened, so it needs no colour of its own) and refuses Enter. KeepOpen leaves the menu up after Do. Value, Expand and Enabled take the same inkbase.Ctx an action or condition takes.
&menu.Menu{
Name: "pause",
ToggleKey: ebiten.KeyEscape,
Root: menu.MenuItem{
Label: inkbase.Plain("PAUSED"),
Items: []menu.MenuItem{
{Label: resume, Do: closeMenu},
{Label: settings, Items: settingsRows},
{Label: quit, Do: action.Quit()},
},
},
}
Keys and the mouse
The menu answers what a list has always answered to, and claims every key it uses, so nothing underneath sees it:
- up and down, or the wheel, walk the rows, wrapping;
- PageUp, PageDown, Home and End jump;
- Enter or right opens the row, or runs it;
- Backspace, left or the right mouse button go back a level, and close at the root;
- Escape, or the close box in the title bar, closes;
- hovering moves the cursor, only when the pointer itself moved, so it does not fight the keys;
- the left mouse button opens the row under the pointer, and the back arrow in the title bar goes back a level.
Only clicks inside the window are consumed, so a menu down one side of the screen leaves the other side playable. ToggleKey opens and closes it; zero binds no key and leaves that to Open, Close and Toggle, which is how a HUD button drives one.
Sticky makes the menu the screen rather than a window over one: it is open whenever it is visible, Escape and back-at-the-root do nothing, ToggleKey is ignored and the close box is not drawn. A main menu at boot has nothing behind it to close onto, and a menu that can be dismissed into an empty frame is a way to lose a game. A sticky menu is what a Stage is usually made of, and its key legend (MenuKeysSticky rather than MenuKeys) offers no Escape. MenuEmpty is what a page with no rows says.
A Menu is Awake: it ticks while a script holds the frame, because a menu that goes deaf when a cutscene starts cannot be closed. It blocks clicks only inside its open window.
The host
The menu reaches its game through Host, which the UICtx's Game must implement: ScreenSize and DrawText (widget.Screen), WidgetInput, Do, MakeCtx and MenuColors() Colors. On a Game that does not, the menu does nothing. inkwell's *Game is a Host, and there the colours come from the active theme's dialog tokens — the background and border, the speaker colour for the title bar, the choice colours for the cursor row, the top bar's accent for the value column — so a menu needs no theme work to match the game.
MenuEmpty, MenuKeys and MenuKeysSticky are package variables a game may replace before the first frame. In an inkwell game the root package's variables of the same names are the ones to change: the engine copies them here every frame.
Index
- Variables
- func FixedValue(t inkbase.Text) func(*inkbase.Ctx) inkbase.Text
- type Colors
- type Host
-
type Menu
- func (m *Menu) BlocksClickAt(ctx *widget.UICtx, p inkbase.Point) bool
- func (m *Menu) Close()
- func (m *Menu) Draw(dst *ebiten.Image, ctx *widget.UICtx)
- func (m *Menu) GetName() string
- func (m *Menu) IsOpen() bool
- func (m *Menu) Layer() widget.Layer
- func (m *Menu) Open()
- func (m *Menu) Tick(ctx *widget.UICtx)
- func (m *Menu) TicksDuringScript() bool
- func (m *Menu) Toggle()
- func (m *Menu) VisibleWhen() inkbase.Condition
- type MenuItem
Variables
var ( // MenuEmpty is printed on a page that has no rows. MenuEmpty = inkbase.Text{ {Lang: "en", Text: "(nothing here)"}, {Lang: "hu", Text: "(nincs itt semmi)"}, } // MenuKeys is the key help in the footer of a menu that can be closed. MenuKeys = inkbase.Text{ {Lang: "en", Text: "↑↓ move ENTER open BACKSPACE back ESC close"}, {Lang: "hu", Text: "↑↓ mozgás ENTER nyit BACKSPACE vissza ESC zár"}, } // MenuKeysSticky is the key help in the footer of a Sticky menu, which // has no close key. MenuKeysSticky = inkbase.Text{ {Lang: "en", Text: "↑↓ move ENTER choose BACKSPACE back"}, {Lang: "hu", Text: "↑↓ mozgás ENTER választ BACKSPACE vissza"}, } )
Texts the Menu prints in its own chrome. A game may replace them before the first frame to change the wording or add a language.
Functions
func FixedValue
func FixedValue(t inkbase.Text) func(*inkbase.Ctx) inkbase.Text
FixedValue is a row's value column when it does not depend on the game: a constant Text in the shape MenuItem.Value asks for.
Types
type Colors
type Colors struct { // BG fills the window. BG color.Color // Border outlines the window and draws its rules. Border color.Color // Text is a row's label and a page's paragraph. Text color.Color // HoverBG fills the row under the cursor. HoverBG color.Color // Hover is the label of the row under the cursor. Hover color.Color // Speaker is the title bar: the arrows and the breadcrumb. Speaker color.Color // Accent is the value column. Accent color.Color // Status is the key help in the footer. Status color.Color }
Colors is the palette a Menu draws with, read from the host every frame.
type Host
type Host interface { widget.Screen // WidgetInput is the frame's consume-on-use input; the menu claims the // keys and clicks it uses. WidgetInput() *input.Input // Do queues the action a leaf row runs. Do(a inkbase.Action) // MakeCtx returns a Ctx over the game's State, for the rows' Value, // Expand and Enabled. MakeCtx() *inkbase.Ctx // MenuColors is the palette the menu draws with. MenuColors() Colors }
Host is what a Menu needs from the game it is ticked and drawn for, the UICtx's Game. On a Game that does not implement it the menu does nothing.
type Menu
type Menu struct { // Name is the key the widget is registered under. Name string // When gates the widget; nil shows it always. When inkbase.Condition // Root is the first page, and its Label is the window's title. Root MenuItem // Bounds is the window. The zero value takes the right half of the screen. Bounds inkbase.Rectangle // ToggleKey opens and closes the menu. Zero binds no key at all, which // leaves Toggle to whatever the game wires it to. ToggleKey ebiten.Key // Sticky makes the menu the screen rather than a window over one: it is // open whenever it is visible, Escape and back-at-the-root do nothing, // and ToggleKey is ignored. A main menu at boot has nothing behind it to // close onto, and a menu that can be dismissed into an empty frame is a // way to lose a game rather than to leave one. Sticky bool // LineHeight is a row's height in pixels; 0 means the glyph height + 2. LineHeight int // Padding is the inner margin in pixels; 0 means 6. Padding int // contains filtered or unexported fields }
Menu is a nested list in a window: a page of rows, any of which can open a page of its own, as deep as the tree goes. It is the whole of the menu system — a debug menu, a pause menu and an in-fiction device screen are this one widget with a different Root.
The keys are the ones a list has always answered to: up and down walk the rows, Enter (or right) opens one, Backspace (or left) goes back a level and closes at the root, Escape closes, PageUp/PageDown/Home/End jump. The mouse does the same: hovering moves the cursor, the wheel scrolls, the left button opens a row, the right button goes back, and the title bar carries a back arrow and a close box. Every key the menu uses it claims, so nothing underneath sees it.
func (*Menu) BlocksClickAt
func (m *Menu) BlocksClickAt(ctx *widget.UICtx, p inkbase.Point) bool
BlocksClickAt reports whether p falls inside the open window, so the verb coin does not open over it. A closed menu blocks nothing.
func (*Menu) Close
func (m *Menu) Close()
Close takes the window down and drops every open page.
func (*Menu) Draw
func (m *Menu) Draw(dst *ebiten.Image, ctx *widget.UICtx)
Draw paints the open window: the title bar with its breadcrumb, back arrow and close box, the visible rows or Body lines with the cursor row highlighted, and the footer listing the keys. A closed menu draws nothing.
func (*Menu) GetName
func (m *Menu) GetName() string
GetName returns the name the menu is registered under.
func (*Menu) IsOpen
func (m *Menu) IsOpen() bool
IsOpen reports whether the window is up.
func (*Menu) Layer
func (m *Menu) Layer() widget.Layer
Layer puts the menu on widget.LayerMenu, above the game it opens over.
func (*Menu) Open
func (m *Menu) Open()
Open puts the window up on its root page. Close takes it down and forgets where the player was, so a menu always opens where it opened last time.
func (*Menu) Tick
func (m *Menu) Tick(ctx *widget.UICtx)
Tick opens a Sticky menu, answers ToggleKey, and while the window is up rebuilds the open page and handles its keys and the mouse, claiming what it uses. A row entered by Enter or a click queues its Do on the game.
func (*Menu) TicksDuringScript
func (m *Menu) TicksDuringScript() bool
TicksDuringScript reports true: a menu has to open, answer its keys and close while a cutscene holds the frame.
func (*Menu) Toggle
func (m *Menu) Toggle()
Toggle closes the window when it is open and opens it on its root page when it is not.
func (*Menu) VisibleWhen
func (m *Menu) VisibleWhen() inkbase.Condition
VisibleWhen returns the condition that switches the menu on; nil means always.
type MenuItem
type MenuItem struct { // Label is the row's text, and the page's crumb once it is entered. Label inkbase.Text // Body makes the page this row opens a paragraph rather than rows: the // Text wrapped to the window, scrolled with the same keys. A codex // entry, a letter, a bio — anything the player reads rather than picks // from. Body inkbase.Text // Value is the right-hand column: what the row is worth right now — a // count, a destination, "held". Nil leaves the column empty. Value func(*inkbase.Ctx) inkbase.Text // Items is the page this row opens, as somebody wrote it. Items []MenuItem // Expand is the page this row opens, built when the player enters it and // rebuilt while it is open. A list read out of a manager or out of the // scene the player is standing in is an Expand; anything an author typed // is Items. Expand func(*inkbase.Ctx) []MenuItem // Do is what Enter queues on a leaf. Do inkbase.Action // Enabled, when it evaluates false, dims the row and refuses Enter. Enabled inkbase.Condition // KeepOpen leaves the menu up after Do ran. The default closes it, which // is what a row that sends the player somewhere wants; a row that toggles // something wants the opposite. KeepOpen bool }
MenuItem is one row of a menu and — when it has children — the page those children make up. A menu is therefore a tree of a single type: the Menu's Root is a MenuItem whose Label titles the window, and every level under it is a MenuItem whose Label is that level's crumb in the title bar.
A page is either written out (Items), worked out when the row is entered (Expand), or a paragraph (Body). A row with none of the three is a leaf, and Enter queues its Do.