# 19. Menus and dialogs Everything in this chapter needs the SDL build and a UI device. Without one each verb refuses by name: ```basic requires=noakgl 10 MENU 1, "START", "QUIT" ``` ```output ? 10 : RUNTIME ERROR MENU needs a UI device and this runtime has none ``` Every picture in this chapter is generated by running the listing above it; see `MAINTENANCE.md` if you are editing one. ## Three widgets, and no fourth A menu the player picks from, a panel that says something, and a line of text pinned to a corner. That is the whole set, and it is the set because it is the set [libakgl](../deps/libakgl/docs/22-ui.md) supplies — these verbs are a thin skin over `akgl_ui_menu`, `akgl_ui_dialog` and `akgl_ui_label`, and they are deliberately not a window toolkit. *What is not here*, at the end, says what to do when you want more. ## MENU ```basic requires=akgl ui=1 screenshot=menu 10 UISTYLE 1, 8, 2 20 MENU 1, "START", "OPTIONS", "QUIT" ``` ![](images/menu.png) `MENU n, item$ [, item$ ...]` puts menu `n` on screen with those entries. `n` runs 1 to 4, so a pause menu can sit over a title menu, and there are at most 16 entries — that is libakgl's own ceiling and not a number invented here. The player moves the highlight with the cursor keys or a D-pad and confirms with Return or a gamepad's south button. The mouse works too: moving onto a row highlights it and clicking one confirms it. A pointer sitting still claims nothing, so a mouse parked over the list does not fight the keyboard for the highlight. Defining a menu **resets its highlight to the first entry and forgets any unread choice.** The entries have just changed meaning; an index into the old list is not worth carrying across, and a stale choice of an entry that no longer exists is worse. `MENU n` with nothing after it retires that menu. `MENU` on its own retires all four — the same convention `SOLID` uses, where no rectangle retires one and no arguments retire them all. ## GETMENU ```basic norun 10 MENU 1, "START", "OPTIONS", "QUIT" 20 GETMENU 1, C% 30 ON C% GOTO 500, 600, 900 ``` `GETMENU n, var` parks the program until somebody chooses, then puts the entry's number in `var` — 1 for the first — and carries on. The variable has to be a numeric one with a sigil, `C%` or `C!`; a bare `C` is a *label* in this dialect and cannot be assigned to at all, which is the same rule `GETKEY` keeps. **Parking is not blocking.** The interpreter never stops returning to its host: while a `GETMENU` is unanswered every step comes straight back without advancing the program, so the frame rate holds, sprites keep moving, `PLAY` keeps draining its queue, and a game that embeds the interpreter stays in control of its own loop. What stops is the program, which is the part you asked for. Two things end the wait without a choice, and both assign 0 rather than leaving the program parked forever: retiring the menu, and the host taking the UI device away. A script waiting on something that no longer exists is worse than a script told nothing happened. ## RMENU ```basic norun 100 REM the same menu, without parking on it 110 IF RMENU(1,1) THEN C% = RMENU(1,0) : GOSUB 800 120 GOSUB 900 : REM move everything else 130 GOTO 110 ``` `RMENU(n, 0)` is the entry currently highlighted. `RMENU(n, 1)` is -1 once the menu has been confirmed and 0 otherwise, **and reading it clears it** — exactly the contract `BUMP()` carries, and for the same reason: without it a program that polls in a loop sees the same choice forever. Reading the highlight does not clear anything. Only field 1 consumes. This is what a game with a loop of its own wants. `GETMENU` is what a title screen wants. ## DIALOG ```basic requires=akgl ui=1 size=480x200 screenshot=dialog 10 UISTYLE 1, 8, 2 20 DIALOG "IT IS PITCH BLACK." ``` ![](images/dialog.png) A panel across the bottom of the screen with wrapped text on it. `DIALOG text$` puts it up; `DIALOG` on its own takes it down. **There is no dismiss key and no return value.** libakgl's dialog has neither — a panel is up because the frame declares it — so showing and hiding is the program's business and `DIALOG` with no argument *is* the dismissal. The usual shape is a `GETKEY` between the two: ```basic norun 10 DIALOG "YOU FOUND A KEY." 20 GETKEY K$ 30 DIALOG ``` The text wraps but does not scroll or page. A message longer than the panel is clipped by it, visibly, which is deliberate on libakgl's side: a clipped line is a bug you can see. ## HUD ```basic requires=akgl ui=1 size=480x200 screenshot=hud 10 UISTYLE 1, 8, 2 20 HUD 1, 0, "LIVES 3" 30 HUD 2, 1, "SCORE 00120" 40 HUD 3, 4, "PAUSED" ``` ![](images/hud.png) `HUD n, anchor, text$` pins a line of text somewhere on the screen. `n` is a slot, 1 to 8, and the anchor is one of five places: | Anchor | Where | |---|---| | 0 | Top left | | 1 | Top right | | 2 | Bottom left | | 3 | Bottom right | | 4 | Centre | Five, and the numbering is not the reading order, because these are libakgl's five `akgl_UiAnchor` values and its numbering. **There is no top-centre or bottom-centre.** A title bar is two corners or the middle. A slot keeps its text until something replaces or retires it, so a score display is one `HUD` per change rather than one per frame. `HUD n` retires slot `n` and `HUD` retires all eight. ## UISTYLE ```basic requires=akgl ui=1 screenshot=uistyle 10 UISTYLE 3, 7, 2, 14, 8 20 MENU 1, "ONE", "TWO" 30 HUD 1, 0, "STYLED" ``` ![](images/uistyle.png) `UISTYLE fill, edge, ink [, padding [, radius]]` sets the one look every widget draws with. The three colours are ordinary palette indices, 1 to 16, the same ones `COLOR` takes. `padding` is the gap in pixels between a panel's edge and its text, and between a `HUD` label and the screen edge it hugs; `radius` rounds the corners, and 0 is square. Padding defaults to 8 and radius to 0. `UISTYLE` with no arguments goes back to libakgl's own default, a dark textbox with parchment ink. It goes back to the *library's* default rather than to a copy of it, so "never styled" and "styled back" stay the same thing. There is one style, shared. Per-widget looks are not implemented — see below. ## Who owns the keyboard **A menu that is up owns the cursor keys and Return.** It has to: those are how a menu is driven, and something has to have them. While at least one menu has entries, Up, Down and Return go to it and never reach `GET`, `GETKEY` or the line editor. Everything else still gets through — letters, Escape, function keys, the gamepad buttons a menu does not use. And retiring the menu gives the keys straight back: ```basic norun 10 MENU 1, "YES", "NO" 20 GETMENU 1, C% 30 MENU 1 : REM now INPUT can have Return again 40 INPUT "NAME"; N$ ``` Forget line 30 and the `INPUT` on line 40 will never see the Return that ends it. That is the one way these verbs can surprise a program, so it is worth the line. ## What is not here The rest of clay. libakgl builds its widgets on a full immediate-mode layout engine — containers, flexible sizing, scrolling regions, images inside panels, custom elements, hit-testing any element — and none of that is reachable from BASIC. A verb per clay concept would be a second language inside this one. Specifically absent, and each on purpose: - **Per-widget styles.** One `UISTYLE`, shared by everything. - **Scrolling.** Neither the dialog nor the menu scrolls; 16 entries is the menu's limit and a longer one wants sub-screens. - **Top-centre and bottom-centre HUD anchors**, because `akgl_UiAnchor` has neither. - **Mouse position, hover state, or clicks on anything but a menu row.** A program cannot ask where the pointer is. - **Any way to dismiss a dialog from inside it.** A game that needs those is writing C against `akgl/ui.h`, which is the route libakgl documents for exactly this case, and it can do that in the same window this interpreter is drawing into — [Chapter 10](10-embedding.md) is how the two meet. One cost worth knowing about: libakgl re-rasterizes every line of widget text every frame. A `HUD` that changes once a level is free; one rebuilt every frame is the expensive case, and a program that has one should say so with `TI#` before blaming the interpreter.