Give BASIC menus, dialogs and HUD labels over libakgl's UI helpers
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m23s
akbasic CI Build / sanitizers (push) Failing after 4m36s
akbasic CI Build / coverage (push) Failing after 3m41s
akbasic CI Build / akgl_build (push) Failing after 4m45s
akbasic CI Build / mutation_test (push) Failing after 3m31s
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m23s
akbasic CI Build / sanitizers (push) Failing after 4m36s
akbasic CI Build / coverage (push) Failing after 3m41s
akbasic CI Build / akgl_build (push) Failing after 4m45s
akbasic CI Build / mutation_test (push) Failing after 3m31s
Group K, and the first verbs to reach the akgl_ui subsystem 0.9.0 brought in: MENU and GETMENU and RMENU, DIALOG, HUD and UISTYLE. A program that wanted a title screen had to draw one out of CHAR and GETKEY, which is what both breakout tutorials make a reader do. The interesting part is the impedance mismatch. libakgl's UI is immediate mode -- widgets are re-declared inside a frame bracket every frame and clay borrows their text until the bracket closes -- and a BASIC program says MENU 1, "START" on line 100 and expects it up on line 900, several hundred frames later. So src/ui_akgl.c is retained on this side and immediate on that one: the record's entry points are setters that copy into akbasic_AkglUi, and akbasic_ui_akgl_render() replays the whole set once a frame from the host's pump. No BASIC string, which lives in the per-line value pool, is ever what clay is handed. The shapes are borrowed rather than invented. MENU retires the way SOLID does -- no entries retires one, no arguments retire them all. GETMENU holds the step loop the way GETKEY does, so parking is not blocking: the step still returns, the host keeps its frame rate, and the sprite, audio and collision services keep running underneath because they run before the blocking checks. RMENU(n,1) reads and clears the way BUMP() does. Withdrawing the device or retiring the menu releases a holding GETMENU with 0 rather than wedging the script, which is akbasic_input_service()'s rule for a withdrawn keyboard. One thing a program has to know, and docs/19-user-interface.md says it twice: a menu that is up owns the cursor keys and Return. It has to, and retiring it gives them back -- forget the MENU n before an INPUT and the INPUT never sees the Return that ends it. akbasic_runtime_set_ui() is its own function rather than a fifth argument to akbasic_runtime_set_devices(), whose signature has twenty-eight call sites in tests and documentation that are about something else. deps/libakgl is not touched. akgl_UiAnchor has the four corners and dead centre, so HUD offers exactly those five; TODO.md records what a top-centre and bottom-centre would cost upstream, along with the three other things this deliberately leaves out. No new error code either -- DEVICE, BOUNDS, SYNTAX and TYPE cover the group, and 520 stays free. tools/screenshot.c had to learn that "needs a font" and "draws the text grid" are two questions. They were one, and a UI figure came out black: the text layer owns every pixel of the rows it covers and painted over the widgets. The new ui=1 fence attribute asks for the first without the second; MAINTENANCE.md documents it. 112/112 in both configurations, 112/112 under ASan and UBSan, coverage 94.1% against the 90% gate with src/runtime_ui.c at 99% of lines and 100% of functions, doxygen clean, and the four new figures byte-identical on a re-render. TODO.md section 8's gate table was stale on several counts besides these and is refreshed with measured numbers. Co-Authored-By: Tachikoma (Claude Code Opus 5 1M) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
#include <akgl/collision.h>
|
||||
#include <akgl/renderer.h>
|
||||
#include <akgl/sprite.h>
|
||||
#include <akgl/ui.h>
|
||||
#include <akgl/version.h>
|
||||
|
||||
/*
|
||||
@@ -76,13 +77,18 @@
|
||||
*
|
||||
* **0.9.0 is the first one that is additive on its face**, and the floor moves
|
||||
* anyway. It is the `akgl_ui` subsystem -- a vendored clay, an arena, menus,
|
||||
* HUDs and dialogs -- reached entirely through a new `akgl/ui.h` this target
|
||||
* does not include, plus three new entry points in `akgl/draw.h`
|
||||
* (`akgl_draw_filled_rounded_rect`, `akgl_draw_arc`, `akgl_draw_set_clip`).
|
||||
* Every header this target actually compiles against -- `actor.h`, `sprite.h`,
|
||||
* `renderer.h`, `text.h`, `registry.h`, `heap.h` -- is byte-identical to 0.8.0,
|
||||
* so unlike 0.8.0 there is no `sizeof` to get wrong. The status band grew again
|
||||
* regardless: `AKGL_ERR_UI` makes seven codes and libakgl now owns 256 to 262.
|
||||
* HUDs and dialogs -- reached through a new `akgl/ui.h`, plus three new entry
|
||||
* points in `akgl/draw.h` (`akgl_draw_filled_rounded_rect`, `akgl_draw_arc`,
|
||||
* `akgl_draw_set_clip`). Every header this target already compiled against --
|
||||
* `actor.h`, `sprite.h`, `renderer.h`, `text.h`, `registry.h`, `heap.h` -- is
|
||||
* byte-identical to 0.8.0, so unlike 0.8.0 there is no `sizeof` to get wrong.
|
||||
* The status band grew again regardless: `AKGL_ERR_UI` makes seven codes and
|
||||
* libakgl now owns 256 to 262.
|
||||
*
|
||||
* `src/ui_akgl.c` reaches the widget helpers -- `akgl_ui_dialog`,
|
||||
* `akgl_ui_label`, `akgl_ui_menu` and the frame bracket -- and nothing else.
|
||||
* The raw clay route is deliberately not exposed to a BASIC program; see
|
||||
* `docs/19-user-interface.md`.
|
||||
*
|
||||
* It also collided with the build rather than the code. 0.9.0 moved eight test
|
||||
* property calls from `set_tests_properties` to `set_property(TEST ...)`, which
|
||||
@@ -113,6 +119,7 @@
|
||||
#include <akbasic/input.h>
|
||||
#include <akbasic/sink.h>
|
||||
#include <akbasic/sprite.h>
|
||||
#include <akbasic/ui.h>
|
||||
|
||||
/**
|
||||
* @brief One full cursor blink in milliseconds, half on and half off.
|
||||
@@ -604,4 +611,117 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_init_akgl(akbasic_AudioBackend
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_input_init_akgl(akbasic_InputBackend *obj);
|
||||
|
||||
/**
|
||||
* @brief State for the libakgl-backed UI backend.
|
||||
*
|
||||
* **This one is retained where the others are not, and that is the point.**
|
||||
* libakgl's UI is immediate mode: widgets are declared inside a frame bracket
|
||||
* and clay borrows their text until the bracket closes. A BASIC program declares
|
||||
* a menu once and expects it to survive several hundred frames. So the verbs
|
||||
* write here, and akbasic_ui_akgl_render() re-declares the lot once a frame from
|
||||
* what it finds. The text is copied on the way in for the same reason.
|
||||
*
|
||||
* Element ids are generated once at init -- "menu1", "hud1" and so on -- because
|
||||
* clay identifies an element by a string that has to be the same every frame for
|
||||
* hover and scroll state to survive between them.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
akgl_RenderBackend *renderer;
|
||||
char fontname[64]; /* registry name this loaded the UI font under */
|
||||
uint16_t fontid; /* what clay calls it */
|
||||
bool ready; /* akgl_ui_init succeeded and shutdown has not run */
|
||||
|
||||
/*
|
||||
* The one style, and whether UISTYLE has set it. When it has not, every
|
||||
* widget is handed NULL and gets libakgl's default rather than a copy of it
|
||||
* made here -- so "never styled" and "styled back to the default" stay the
|
||||
* same thing.
|
||||
*/
|
||||
akgl_UiStyle style;
|
||||
bool styled;
|
||||
|
||||
char dialogtext[AKBASIC_MAX_STRING_LENGTH];
|
||||
bool dialogopen;
|
||||
|
||||
char labelid[AKBASIC_UI_MAX_LABELS][8];
|
||||
char labeltext[AKBASIC_UI_MAX_LABELS][AKBASIC_MAX_STRING_LENGTH];
|
||||
int labelanchor[AKBASIC_UI_MAX_LABELS];
|
||||
bool labelset[AKBASIC_UI_MAX_LABELS];
|
||||
|
||||
char menuid[AKBASIC_UI_MAX_MENUS][8];
|
||||
char menuitems[AKBASIC_UI_MAX_MENUS][AKBASIC_UI_MAX_MENU_ITEMS][AKBASIC_MAX_STRING_LENGTH];
|
||||
akgl_UiMenu menus[AKBASIC_UI_MAX_MENUS];
|
||||
} akbasic_AkglUi;
|
||||
|
||||
/**
|
||||
* @brief Bring the UI subsystem up and point a UI backend at it.
|
||||
*
|
||||
* Loads @p fontpath into the font registry under a name of its own and registers
|
||||
* that name with clay. **It calls akgl_registry_init_font(), which
|
||||
* akgl_registry_init() deliberately does not** -- nothing else in this
|
||||
* repository has needed the font registry, because the text sink opens its font
|
||||
* with a bare TTF_OpenFont.
|
||||
*
|
||||
* @warning akgl_ui_init() is process-global and refuses a second call. One UI
|
||||
* per process: two runtimes in one process share it.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param state Storage for the retained widget set; must outlive the backend.
|
||||
* @param renderer The renderer the host already initialized; not created here.
|
||||
* @param fontpath Font file the widgets draw with. Required.
|
||||
* @param fontsize Point size to load it at.
|
||||
* @param width Layout width, in pixels. Usually the window's.
|
||||
* @param height Layout height, in pixels.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When `obj`, `state`, `renderer` or `fontpath` is NULL.
|
||||
* @throws AKBASIC_ERR_VALUE When `fontsize`, `width` or `height` is not positive.
|
||||
* @throws AKGL_ERR_UI When the subsystem is already up, or clay refuses.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_init_akgl(akbasic_UiBackend *obj, akbasic_AkglUi *state, akgl_RenderBackend *renderer, const char *fontpath, int fontsize, int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Declare and draw the whole retained widget set, once, for this frame.
|
||||
*
|
||||
* Separate from the verbs for the same reason akbasic_sprite_akgl_render() is:
|
||||
* the interpreter does not own the frame. A host calls this when it is drawing,
|
||||
* after everything else -- the UI goes on top.
|
||||
*
|
||||
* A frame that fails still closes its bracket, so one bad frame is one bad frame
|
||||
* rather than a wedged subsystem.
|
||||
*
|
||||
* @param obj The backend to draw.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When `obj` is NULL or carries no state.
|
||||
* @throws AKGL_ERR_UI When clay reported a layout error.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_akgl_render(akbasic_UiBackend *obj);
|
||||
|
||||
/**
|
||||
* @brief Offer one SDL event to the UI, and say whether it was taken.
|
||||
*
|
||||
* The mouse goes to the subsystem, which never consumes a keystroke. The
|
||||
* keyboard and gamepad go to each menu that has entries, and those **do**
|
||||
* consume Up, Down and Return -- so a host must route this before it feeds the
|
||||
* keystroke ring, and a menu that is up owns those keys. Retiring the menu gives
|
||||
* them back.
|
||||
*
|
||||
* @param obj The backend to offer the event to.
|
||||
* @param event The event; not modified.
|
||||
* @param consumed Output destination populated by the function.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When any argument is NULL, or `obj` carries no state.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_akgl_handle_event(akbasic_UiBackend *obj, SDL_Event *event, bool *consumed);
|
||||
|
||||
/**
|
||||
* @brief Take the UI subsystem down and unload the font it loaded.
|
||||
*
|
||||
* Idempotent, and never fails: it is called from teardown paths that are already
|
||||
* unwinding. Call it before the renderer goes.
|
||||
*
|
||||
* @param obj The backend to shut down; NULL is a no-op.
|
||||
*/
|
||||
void akbasic_ui_akgl_shutdown(akbasic_UiBackend *obj);
|
||||
|
||||
#endif // _AKBASIC_AKGL_H_
|
||||
|
||||
@@ -86,6 +86,8 @@ typedef struct akbasic_AkglFrontend
|
||||
akbasic_InputBackend input;
|
||||
akbasic_SpriteBackend sprites;
|
||||
akbasic_AkglSprites spritesstate;
|
||||
akbasic_UiBackend ui;
|
||||
akbasic_AkglUi uistate;
|
||||
|
||||
/** False once the window has been closed; the drive loop stops on it. */
|
||||
bool running;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <akbasic/structtype.h>
|
||||
#include <akbasic/symtab.h>
|
||||
#include <akbasic/types.h>
|
||||
#include <akbasic/ui.h>
|
||||
#include <akbasic/value.h>
|
||||
#include <akbasic/variable.h>
|
||||
|
||||
@@ -153,6 +154,13 @@ typedef struct akbasic_Runtime
|
||||
akbasic_AudioBackend *audio;
|
||||
akbasic_InputBackend *input;
|
||||
akbasic_SpriteBackend *sprites;
|
||||
/*
|
||||
* The fifth one, set on its own through akbasic_runtime_set_ui() rather than
|
||||
* as a fifth argument to akbasic_runtime_set_devices(). Adding a parameter
|
||||
* would have rewritten twenty-eight call sites to pass a NULL none of them
|
||||
* cares about, in tests and documentation that are about something else.
|
||||
*/
|
||||
akbasic_UiBackend *ui;
|
||||
|
||||
/*
|
||||
* The graphics verbs' own state -- mode, color-source bindings, pixel cursor
|
||||
@@ -172,6 +180,9 @@ typedef struct akbasic_Runtime
|
||||
/* GETKEY's hold on the step loop. Same reasoning again: it is the program's. */
|
||||
akbasic_InputState input_state;
|
||||
|
||||
/* Which menus are up, and GETMENU's hold on the step loop. */
|
||||
akbasic_UiState ui_state;
|
||||
|
||||
/*
|
||||
* The eight sprites, their positions and their collision bits. Same
|
||||
* reasoning as gfx and audio_state, and one more: RSPPOS and RSPRITE read
|
||||
@@ -321,6 +332,21 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_init(akbasic_Runtime *obj, ak
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_devices(akbasic_Runtime *obj, akbasic_GraphicsBackend *graphics, akbasic_AudioBackend *audio, akbasic_InputBackend *input, akbasic_SpriteBackend *sprites);
|
||||
|
||||
/**
|
||||
* @brief Attach the UI backend, where MENU, DIALOG, HUD and UISTYLE land.
|
||||
*
|
||||
* Separate from akbasic_runtime_set_devices() rather than a fifth argument to
|
||||
* it, because that signature has twenty-eight call sites and none of them is
|
||||
* about the UI. NULL withholds the capability, which is what the standalone
|
||||
* stdio driver does and what every no-SDL build gets.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param ui Where MENU, DIALOG, HUD and UISTYLE land; may be NULL.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_ui(akbasic_Runtime *obj, akbasic_UiBackend *ui);
|
||||
|
||||
/**
|
||||
* @brief Tell the interpreter where the program it is running came from.
|
||||
*
|
||||
|
||||
179
include/akbasic/ui.h
Normal file
179
include/akbasic/ui.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* @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 TODO.md.
|
||||
*/
|
||||
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_
|
||||
Reference in New Issue
Block a user