Add the akgl_ui subsystem: clay-backed menus, HUDs and dialogs #3

Merged
andrew merged 15 commits from clay-ui into main 2026-08-02 16:14:50 -04:00
2 changed files with 23 additions and 2 deletions
Showing only changes of commit f2efbdf1b7 - Show all commits

View File

@@ -28,6 +28,15 @@ declarative blocks yourself between the same two frame calls, with the whole DSL
available. The demo this chapter quotes, [`examples/uidemo`](../examples/uidemo), does
both: its title menu and HUD are widgets, its options screen is raw `CLAY()`.
**What this chapter assumes.** A program that already brings libakgl up — the
`akgl_game_init` startup order from [Chapter 7](07-the-game-and-the-frame.md) (or
[Chapter 3](03-getting-started.md)'s smallest window), plus one loaded font from
[Chapter 17](17-text-and-fonts.md). None of that is restated here, and the UI adds only
three calls to it, shown below. When something in *your* bring-up fights you, the demo's
`startup()` in [`examples/uidemo/uidemo.c`](../examples/uidemo/uidemo.c) is the complete
working sequence to diff against — including the `main()` shape, which the runnable
snippets in this manual deliberately hide.
Like collision, the subsystem is optional at runtime rather than at build time: it is
always compiled in, costs static storage until `akgl_ui_init` runs, and a game that
never calls that pays nothing else.
@@ -242,8 +251,13 @@ static akgl_UiMenu title_menu = {
Declaring it each frame is `akgl_ui_menu(&title_menu)`. Input reaches it two ways, and
they meet in the same struct: `akgl_ui_menu_handle_event` moves `selected` from the
arrow keys and D-pad (wrapping at both ends) and sets `activated` on Return or gamepad
South; the mouse selects by *moving onto* a row and activates by clicking one, during
the declaration itself. A stationary pointer claims nothing — parking the mouse over the
South — exactly `SDLK_UP`, `SDLK_DOWN` and `SDLK_RETURN` against `SDL_Event.key.key`,
and `SDL_GAMEPAD_BUTTON_DPAD_UP`, `_DPAD_DOWN` and `_SOUTH` against
`SDL_Event.gbutton.button`. Keycodes, not scancodes — which only matters when you
*synthesize* events, as the demo's `--demo` script does: a hand-built key event must
fill in `key.key`, or it matches nothing and the menu silently ignores it. The mouse
selects by *moving onto* a row and activates by clicking one, during the declaration
itself. A stationary pointer claims nothing — parking the mouse over the
menu must not pin the selection against the keyboard, which would otherwise fight it
sixty times a second and lose.

View File

@@ -391,6 +391,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_ui_frame_end(akgl_RenderBackend *self);
* -- clay keeps the pointer, not a copy. May be empty, which
* draws an empty panel.
* @param style Look to draw with, or `NULL` for the textbox palette.
*
* @note The panel's height is fixed at #AKGL_UI_DIALOG_HEIGHT and the text is
* not clipped to it: a message that wraps to more lines than fit runs
* past the bottom border, visibly. That is the same behaviour as the
* hand-rolled panel this widget mirrors, and it is deliberate --
* overflow you can see beats truncation you cannot. Size the height for
* your longest message, or raise the override.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p id or @p text is `NULL`.
* @throws AKGL_ERR_UI If the subsystem is not initialized or no frame is