# 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.
