# UI Dusk's UI system (buttons, checkboxes, sliders, dropdowns, tabs, menus, focus navigation) is a **C-only API**. It is not exposed to JerryScript — see [SCRIPTING.md](SCRIPTING.md) for what scripts *can* touch. If you need a script to open/react to a menu, wire it through a C callback or a game-side flag scripts can poll; there's no bridge for this today. ## Mental model There is no retained-mode UI tree, no automatic dispatch, no scissor/clip-rect API. Every widget is a plain struct you own (usually as a global or scene-owned variable). You call its `xxxInit(...)` once, then call its `xxxDraw(widget, x, y)` yourself, every frame you want it visible, at whatever screen position you choose. Nothing draws itself automatically except three fixed system overlays (overscan bars, debug console, FPS counter) — see [System overlays](#system-overlays-automatic) below. ### Where UI rendering happens in the frame - `uiInit()` / `uiDispose()` run once, at engine startup/shutdown. - `uiUpdate()` runs once per tick (drives focus-navigation input handling). - `uiRender()` runs once per frame, called from inside `sceneRender()` — i.e. **after** the active scene's own 3D/game-world rendering, using an orthographic screen-space projection. Your own widget `xxxDraw()` calls should happen around the same point — typically from your scene's render callback, after world content, so UI draws on top. ## Widgets Every widget follows the same shape: `xxxInit(widget, ...)` zeroes the struct and sets its fields; `xxxDraw(const widget*, x, y) -> errorret_t` draws it at that screen position. > **Init before Draw.** `uislider_t`, `uidropdown_t`, and `uitab_t` cache > their label's measured width/height at `Init` time (an optimization — > label text doesn't change after that point). Calling `Draw` before `Init`, > or mutating `->label` directly instead of re-initializing, leaves stale > layout. `uibutton_t`/`uicheckbox_t` don't have this restriction. ### Button ```c void uiButtonInit(uibutton_t *button, const char_t *label); bool_t uiButtonIsHighlighted(const uibutton_t *button); void uiButtonSetHighlighted(uibutton_t *button, bool_t highlighted); errorret_t uiButtonDraw(const uibutton_t *button, float_t x, float_t y); ``` Draws `label` in red when highlighted, white otherwise. ### Checkbox ```c void uiCheckboxInit(uicheckbox_t *checkbox, const char_t *label); bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox); void uiCheckboxSetChecked(uicheckbox_t *checkbox, bool_t checked); void uiCheckboxToggle(uicheckbox_t *checkbox); bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox); void uiCheckboxSetHighlighted(uicheckbox_t *checkbox, bool_t highlighted); errorret_t uiCheckboxDraw(const uicheckbox_t *checkbox, float_t x, float_t y); ``` Draws `"Y "`/`"N "` then the label. ### Slider ```c typedef union { float_t f; int32_t i; } uislidervalue_t; void uiSliderInitFloat(uislider_t*, const char_t *label, float_t value, float_t min, float_t max, float_t step); void uiSliderInitInt(uislider_t*, const char_t *label, int32_t value, int32_t min, int32_t max, int32_t step); float_t uiSliderGetFloat(const uislider_t*); // works for either type int32_t uiSliderGetInt(const uislider_t*); // asserts type == INT void uiSliderSetFloat(uislider_t*, float_t value); // asserts type == FLOAT, clamps void uiSliderSetInt(uislider_t*, int32_t value); // asserts type == INT, clamps void uiSliderStepUp(uislider_t*); // wraps to min past max void uiSliderStepDown(uislider_t*); // wraps to max past min float_t uiSliderGetRatio(const uislider_t*); // normalized 0..1 int32_t uiSliderGetStepCount(const uislider_t*); // 0 for float sliders bool_t uiSliderIsHighlighted(const uislider_t*); void uiSliderSetHighlighted(uislider_t*, bool_t highlighted); errorret_t uiSliderDraw(const uislider_t*, float_t x, float_t y); ``` Draws label, a track, a fill proportional to the current ratio, discrete step markers if it's an int slider with fewer than 10 steps, then the value as text. ### Dropdown ```c void uiDropdownInit(uidropdown_t *dropdown, const char_t *label, const char_t *const *options, uint8_t optionCount, uint8_t selectedIndex); uint8_t uiDropdownGetSelectedIndex(const uidropdown_t *dropdown); const char_t *uiDropdownGetSelectedOption(const uidropdown_t *dropdown); void uiDropdownSetSelectedIndex(uidropdown_t *dropdown, uint8_t index); void uiDropdownStepNext(uidropdown_t *dropdown); // wraps void uiDropdownStepPrev(uidropdown_t *dropdown); // wraps bool_t uiDropdownIsHighlighted(const uidropdown_t *dropdown); void uiDropdownSetHighlighted(uidropdown_t *dropdown, bool_t highlighted); errorret_t uiDropdownDraw(const uidropdown_t *dropdown, float_t x, float_t y); ``` `options` is a caller-owned array of strings that must outlive the dropdown (it isn't copied). Draws `label` then `"< Option >"`. ### Tab ```c void uiTabInit(uitab_t *tab, const char_t *label); bool_t uiTabIsActive(const uitab_t *tab); void uiTabSetActive(uitab_t *tab, bool_t active); errorret_t uiTabDraw(const uitab_t *tab, float_t x, float_t y); ``` Draws a background box sized to the label (green if active, red if inactive) with the label on top. ## Menus: assembling widgets into a navigable list `uimenu_t` is the one aggregate widget — it owns an array of items (labels, spacers, and any of the widgets above), lays them out in a grid, and wires keyboard/gamepad navigation via the focus system for you. ```c typedef enum { UI_MENU_WIDGET_TYPE_NONE, UI_MENU_WIDGET_TYPE_LABEL, UI_MENU_WIDGET_TYPE_SPACER, UI_MENU_WIDGET_TYPE_CHECKBOX, UI_MENU_WIDGET_TYPE_BUTTON, UI_MENU_WIDGET_TYPE_TAB, UI_MENU_WIDGET_TYPE_SLIDER, UI_MENU_WIDGET_TYPE_DROPDOWN, } uimenuwidgettype_t; void uiMenuInit(uimenu_t *menu, uimenuselectedcallback_t selected, uimenuclosedcallback_t closed, uimenuchangedcallback_t changed); void uiMenuSetItems(uimenu_t *menu, const uimenuitem_t *items, uint8_t itemCount, uint8_t columns); void uiMenuSetPosition(uimenu_t *menu, uint8_t x, uint8_t y); // focus cursor cell, not pixels void uiMenuOpen(uimenu_t *menu); // pushes onto the focus stack void uiMenuClose(uimenu_t *menu); // pops it bool_t uiMenuIsActive(const uimenu_t *menu); errorret_t uiMenuDraw(const uimenu_t *menu, float_t x, float_t y, float_t width, float_t height); ``` - `selected(menu, index, item)` fires when the player presses accept on an item. - `changed(menu, index, item)` fires when the highlighted item changes. - `closed(menu)` fires when the menu is popped off the focus stack. - LEFT/RIGHT on a highlighted slider/checkbox/dropdown adjusts its value in place instead of moving focus off it (handled internally). ### Building a menu with the `MENU_*` macros `uimenu.h` provides macros that cut the boilerplate of filling in a `uimenuitem_t` array. They expand into statements using local variables named `menu`, `menuIndex`, and `menuCapacity`, so use them together, inside one function, starting with `MENU_BEGIN` and ending with `MENU_END`: ```c static uimenuitem_t optionsItems[8]; static uimenu_t optionsMenu; static const char_t *qualityOptions[] = { "Low", "Medium", "High" }; static void onOptionsSelected( const uimenu_t *menu, const uint8_t index, const uimenuitem_t *item ) { if(index == 4) uiMenuClose(&optionsMenu); // "Back" button } static void onOptionsClosed(const uimenu_t *menu) { // e.g. return to the previous screen } void optionsMenuBuild(void) { MENU_BEGIN(&optionsMenu, optionsItems, onOptionsSelected, onOptionsClosed, NULL); MENU_LABEL("Options"); MENU_CHECKBOX("Fullscreen"); MENU_SLIDER_FLOAT("Volume", 0.8f, 0.0f, 1.0f, 0.05f); MENU_DROPDOWN("Quality", qualityOptions, 3, 1); MENU_BUTTON("Back"); MENU_END(optionsItems, 1); } // Once, when the menu screen becomes active: uiMenuOpen(&optionsMenu); // Every frame the menu should be visible: uiMenuDraw(&optionsMenu, 20.0f, 20.0f, 200.0f, 100.0f); // When leaving the menu screen: uiMenuClose(&optionsMenu); ``` `MENU_LABEL`/`MENU_SPACER` force a row break and aren't focusable/selectable. Every other `MENU_*` macro calls the matching widget's own `Init` for you. > This example is constructed directly from the widget/menu API surface (all > function and macro signatures above are verified against the source), but > there's currently no real menu-building call site anywhere else in the > engine to cross-check the *pattern* against — treat it as a starting point, > not a copy of shipped code. ## Focus system: navigation underneath `uimenu` If you're building a custom widget that needs keyboard/gamepad navigation without going through `uimenu`, use `ui/focus/uifocus.h` directly. `uimenu` is implemented entirely in terms of this API, so it's a reasonable reference. ```c uifocusitem_t * uiFocusPush( uint8_t cols, uint8_t rows, uifocusitemcallback_t selected, // fires on accept uifocusitemcallback_t changed, // fires on cursor move (and once immediately) uifocusitemcallback_t closed, // fires on pop uifocusitemdirectioncallback_t direction, // optional pre-empt of a direction press; NULL for default grid movement void *user ); void uiFocusPop(void); void uiFocusPopItem(uifocusitem_t *item); void uiFocusSetPosition(uifocusitem_t *item, uint8_t x, uint8_t y); // wraps void uiFocusMoveDirection(uifocusitem_t *item, uifocusdirection_t dir); ``` `uiFocusUpdate()` runs automatically from `uiUpdate()` every tick — you don't call it yourself. It reads `INPUT_ACTION_ACCEPT` (fires `selected`), `INPUT_ACTION_CANCEL` (pops the stack), and the four directional actions (with hold-to-repeat timing) to move the cursor within the topmost pushed item. Only the topmost stack entry (max depth 8) receives input at a time — opening a submenu means pushing a new focus item on top; closing it pops back to the parent. There's no separate "is this widget focused" query — "focused" is expressed as the pushed item's current `(x, y)` cursor cell matching a given slot, which is exactly how `uimenu`'s `changed` callback decides which item to highlight. ## System overlays (automatic) Three small overlays are wired into a fixed internal list and draw themselves every frame with no call needed from game code: - **Overscan bars** (`ui/overlay/uicrop.h`) — draws opaque bars over the screen area outside `SCREEN.scanX/scanY/scanWidth/scanHeight` (the overscan-safe viewport). A no-op on platforms/configs where the scan area already equals the full viewport. `UI_CROP.color` (default black) is the only thing you'd normally touch here. - **Debug console** (`ui/debug/uiconsole.h`) — draws console history when visible. - **FPS counter** (`ui/debug/uifps.h`) — draws a live FPS/frame-time readout. None of these have a scissor/clip-rect equivalent for your own widgets — there is no clipping API in this UI system; everything draws unclipped at whatever position you give it.