Some checks failed
akbasic CI Build / cmake_build (push) Failing after 4m7s
akbasic CI Build / coverage (push) Failing after 3m33s
akbasic CI Build / akgl_build (push) Failing after 4m39s
akbasic CI Build / mutation_test (push) Failing after 3m32s
akbasic CI Build / sanitizers (push) Failing after 14m32s
Its own SS0-SS9 structure survived the move to the tracker, so 133 citations still resolve and are left alone. Thirteen did not. Six named another repository's TODO.md by a section number: libakstdlib 1.6, 2.2.2 and 2.3, and libakgl's file. Those repositories dropped their numbering entirely, so each now names UPGRADING.md, an issue, or the tracker. Three cited 'TODO.md section 12', which has never existed here -- the defect list is SS6. TODO.md itself caught two others of that class earlier. The CMakeLists comment carried a stale premise with it: eleven defects 'deliberately reproduced and not yet fixed' stopped being the rule when SS0.1 retired the fidelity constraint. Four told a reader to record work in TODO.md; the scanner defect is issue #4, the audio_tables mutation gap is #25, and the UI gaps are libakgl #79 and #80. Verified: cmake --build build && ctest --test-dir build, 112/112. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
180 lines
7.7 KiB
C
180 lines
7.7 KiB
C
/**
|
|
* @file ui.h
|
|
* @brief Declares the UI backend: where MENU, DIALOG, HUD and UISTYLE land.
|
|
*
|
|
* Same reasoning as graphics.h, audio.h and input.h -- the core library is free
|
|
* of SDL and builds with no libakgl present, so the UI verbs call through a
|
|
* record of function pointers rather than reaching akgl_ui_* directly.
|
|
*
|
|
* One thing here is unlike the other four groups, and it shapes the whole
|
|
* record. **libakgl's UI is immediate mode and this interpreter is not.** A game
|
|
* written against akgl/ui.h re-declares its widgets inside a frame bracket sixty
|
|
* times a second and clay borrows the text pointers until the bracket closes; a
|
|
* BASIC program says `MENU 1, "START", "QUIT"` once on line 100 and expects it to
|
|
* still be there on line 900, several hundred frames later. So the entry points
|
|
* below are all *setters*: a verb pushes what it wants into storage the backend
|
|
* owns, and the backend re-declares the whole set once a frame from that. A
|
|
* BASIC string is transient by construction and is never what clay is handed.
|
|
*
|
|
* A runtime with no UI backend is the normal case -- the standalone driver has
|
|
* none -- so every UI verb refuses with AKBASIC_ERR_DEVICE rather than
|
|
* dereferencing a NULL vtable.
|
|
*/
|
|
|
|
#ifndef _AKBASIC_UI_H_
|
|
#define _AKBASIC_UI_H_
|
|
|
|
#include <akerror.h>
|
|
|
|
#include <akbasic/graphics.h>
|
|
#include <akbasic/types.h>
|
|
|
|
/**
|
|
* @brief How many menus a program may have up at once.
|
|
*
|
|
* Four rather than one because a pause menu over a title menu is an ordinary
|
|
* thing to want, and rather than eight because nothing needs eight and each one
|
|
* costs its item storage in the backend.
|
|
*/
|
|
#define AKBASIC_UI_MAX_MENUS 4
|
|
|
|
/** @brief How many HUD label slots there are. Eight, the way there are eight sprites. */
|
|
#define AKBASIC_UI_MAX_LABELS 8
|
|
|
|
/**
|
|
* @brief Most entries one MENU may carry.
|
|
*
|
|
* This is libakgl's AKGL_UI_MENU_MAX_ITEMS, restated rather than included: the
|
|
* core library must not include an akgl header, and a menu longer than a screen
|
|
* is a different widget anyway. src/ui_akgl.c asserts the two agree at compile
|
|
* time, so raising one without the other is a build error and not a surprise.
|
|
*/
|
|
#define AKBASIC_UI_MAX_MENU_ITEMS 16
|
|
|
|
/**
|
|
* @brief Where on the screen a HUD label pins itself.
|
|
*
|
|
* These are akgl_UiAnchor's five values and its numbering, so the adaptor maps
|
|
* them across without a table. **There is deliberately no top-centre or
|
|
* bottom-centre**, because libakgl has neither and this repository does not edit
|
|
* its dependencies to add one; see libakgl issues #79 and #80.
|
|
*/
|
|
typedef enum
|
|
{
|
|
AKBASIC_UI_ANCHOR_TOP_LEFT = 0,
|
|
AKBASIC_UI_ANCHOR_TOP_RIGHT,
|
|
AKBASIC_UI_ANCHOR_BOTTOM_LEFT,
|
|
AKBASIC_UI_ANCHOR_BOTTOM_RIGHT,
|
|
AKBASIC_UI_ANCHOR_CENTER,
|
|
AKBASIC_UI_ANCHOR_LIMIT /* one past the last; not an anchor */
|
|
} akbasic_UiAnchor;
|
|
|
|
/**
|
|
* @brief Where the UI verbs draw.
|
|
*
|
|
* Every entry point is required. There is no optional one -- akbasic_GraphicsBackend
|
|
* has one only because `size` was added after hosts already existed, and this
|
|
* record has no such history.
|
|
*
|
|
* Text arguments are **copied** by the implementation, not borrowed. A BASIC
|
|
* string lives in a value from the per-line pool and is gone by the next
|
|
* statement, let alone the next frame.
|
|
*/
|
|
typedef struct akbasic_UiBackend
|
|
{
|
|
void *self;
|
|
|
|
/** Set the dialog panel's text. NULL or empty retires the panel. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*dialog)(struct akbasic_UiBackend *self, const char *text);
|
|
|
|
/**
|
|
* Set one HUD label slot. NULL text retires the slot.
|
|
*
|
|
* `slot` is zero-based here; BASIC numbers them from one, and the verb does
|
|
* that subtraction. `anchor` is an akbasic_UiAnchor, already range-checked.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*label)(struct akbasic_UiBackend *self, int slot, int anchor, const char *text);
|
|
|
|
/**
|
|
* Define one menu, replacing whatever it held. `count` 0 retires it.
|
|
*
|
|
* Defining a menu resets its highlight to the first entry and clears its
|
|
* activation latch: the program has just changed what the entries mean, so
|
|
* a selection index into the old list is not worth carrying over.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*menu)(struct akbasic_UiBackend *self, int slot, const char *const *items, int count);
|
|
|
|
/**
|
|
* Read a menu's highlight and its activation latch.
|
|
*
|
|
* `selected` comes back zero-based. `activated` is true when the entry has
|
|
* been confirmed since the latch was last cleared, and `clear` clears it --
|
|
* which is what makes RMENU(n,1) a read-and-clear the way BUMP() is. A menu
|
|
* that was never defined reports 0 and false rather than failing: a program
|
|
* polling a menu it has retired has asked a reasonable question.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*menu_state)(struct akbasic_UiBackend *self, int slot, int *selected, bool *activated, bool clear);
|
|
|
|
/**
|
|
* Set the one style every widget draws with.
|
|
*
|
|
* `padding` and `radius` are pixels. Restoring the library default is
|
|
* `style` with a NULL fill -- see akbasic_ui_style_default().
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*style)(struct akbasic_UiBackend *self, akbasic_Color *fill, akbasic_Color *edge, akbasic_Color *ink, double padding, double radius);
|
|
|
|
/** Retire every widget and restore the default style. NEW and CLR call this. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*clear)(struct akbasic_UiBackend *self);
|
|
} akbasic_UiBackend;
|
|
|
|
/**
|
|
* @brief The UI verbs' own state, which lives on the runtime.
|
|
*
|
|
* Deliberately small. The widget text is in the backend, because the backend is
|
|
* what has to hand clay a pointer that outlives a frame; what is left here is
|
|
* what the *interpreter* needs to answer questions about, which is which menus
|
|
* exist and whether a GETMENU is holding.
|
|
*/
|
|
typedef struct
|
|
{
|
|
bool menudefined[AKBASIC_UI_MAX_MENUS]; /* MENU n has entries, so it is on screen */
|
|
bool waiting; /* a GETMENU is holding the program */
|
|
int waitmenu; /* ...on this menu, zero-based */
|
|
char variable[64]; /* ...to assign the chosen entry to */
|
|
} akbasic_UiState;
|
|
|
|
/* --- Internal API: exposed for the step loop and the tests. --- */
|
|
|
|
struct akbasic_Runtime;
|
|
|
|
/**
|
|
* @brief Reset the UI state to its power-on values: no menus, nothing holding.
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_state_init(akbasic_UiState *obj);
|
|
|
|
/**
|
|
* @brief Answer whether a GETMENU is still holding, assigning the entry if one was chosen.
|
|
*
|
|
* Called from akbasic_runtime_step() beside akbasic_input_service() and for the
|
|
* same reason: waiting must not mean blocking. While `*blocked` comes back true
|
|
* the step executes no source line, so the program stays where the GETMENU left
|
|
* it, and the sprite, audio and collision services -- which run *before* this --
|
|
* keep running underneath it.
|
|
*
|
|
* Two things release the hold rather than wedging the script, both of them the
|
|
* rules akbasic_input_service() already keeps: taking the UI device away, and
|
|
* retiring the menu that was being waited on. Either assigns 0.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param blocked Output destination populated by the function; true while a GETMENU is unsatisfied.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When either argument is NULL.
|
|
* @throws AKBASIC_ERR_UNDEFINED When the variable the GETMENU named has gone out of scope.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_service(struct akbasic_Runtime *obj, bool *blocked);
|
|
|
|
#endif // _AKBASIC_UI_H_
|