Files
dusk/.claude/ui.md
T
2026-06-18 14:59:21 -05:00

3.8 KiB

UI System

Source: src/dusk/ui/

The UI system is an immediate-mode layer drawn on top of the game scene each frame. Elements register themselves; uiUpdate() ticks all elements and uiRender() draws them. All coordinates are in screen pixels.


Lifecycle

uiInit() → uiTextboxInit()   // init order matters — textbox depends on display being ready
uiUpdate()                   // each frame, before rendering
uiRender()                   // each frame, after scene render
uiDispose()
uiTextboxDispose()

Element registration (ui/uielement.h)

Elements are stored in UI_ELEMENTS[]. Each has a type and a draw callback:

typedef struct {
  uielementtype_t type;
  errorret_t (*draw)();
} uielement_t;

Currently UI_ELEMENT_TYPE_NATIVE elements call their draw function directly. New debug/HUD elements are registered by adding to this array.


Textbox (ui/uitextbox.h)

UI_TEXTBOX is the global dialogue box. It word-wraps text, paginates it, and plays a typewriter scroll effect.

uiTextboxSetText("Long dialogue string...");  // wraps to charsPerLine, paginates
uiTextboxUpdate();                            // each frame: advance scroll, check input
uiTextboxDraw();                              // draw box + text

// Pagination:
uiTextboxPageIsComplete()   // true when all chars of current page are visible
uiTextboxHasNextPage()
uiTextboxNextPage()

// Subscibe to page events:
eventSubscribe(&UI_TEXTBOX.onPageComplete, cb, user);
eventSubscribe(&UI_TEXTBOX.onLastPage, cb, user);

UI_TEXTBOX.advanceAction defaults to the input action that advances dialogue — set it before calling uiTextboxInit() if the default doesn't suit.

Layout constants:

  • UI_TEXTBOX_TEXT_MAX — 1024 chars
  • UI_TEXTBOX_LINES_MAX — 64 lines
  • UI_TEXTBOX_LINES_PER_PAGE_MAX — 3 lines visible at once
  • UI_TEXTBOX_SCROLL_CHARS_PER_TICK — 1 char per tick (typewriter speed)

The textbox uses UI_TEXTBOX.frame (a uiframe_t) for its border rendering and UI_TEXTBOX.font for text.


UI Frame (ui/uiframe.h)

9-slice bordered box rendered with a tileset:

uiFrameInit(&frame);
uiFrameDraw(&frame, x, y, width, height);
uiFrameDispose(&frame);

The tileset is loaded from the asset system during uiFrameInit(). The 9 slices are arranged in the tileset grid as: top-left corner, top edge, top-right corner, left edge, fill, right edge, bottom-left, bottom edge, bottom-right.


Loading overlay (ui/uiloading.h)

UI_LOADING is a full-screen loading indicator with fade-in/fade-out transitions:

uiLoadingShow(onShownCallback, user);   // fade in; calls callback when fully opaque
uiLoadingHide(onHiddenCallback, user);  // fade out; calls callback when fully transparent
uiLoadingUpdate(delta);                 // each frame
uiLoadingDraw();                        // each frame, over everything

UI_LOADING_FADE_DURATION is FIXED(0.5f) seconds. Subscribe to UI_LOADING.onTransitionEnd for completion events.


Full-box overlay (ui/uifullbox.h)

Two global full-screen color overlays: UI_FULLBOX_UNDER (drawn before game content) and UI_FULLBOX_OVER (drawn after). Used for scene transitions (fade to black, etc.):

uiFullboxTransition(
  &UI_FULLBOX_OVER,
  COLOR_TRANSPARENT, COLOR_BLACK,
  FIXED(0.5f),
  EASING_IN_OUT_CUBIC
);
eventSubscribe(&UI_FULLBOX_OVER.onTransitionEnd, myCallback, NULL);

uiFullboxUnderDraw();   // draw under layer
uiFullboxOverDraw();    // draw over layer

FPS counter (ui/uifps.h)

UIFPS tracks a rolling average FPS. uiFPSDraw() renders it in the corner. Currently drawn as part of the debug HUD (not wired to an element slot).


Player position HUD (ui/uiplayerpos.h)

uiplayerpos.c draws the player's current world tile coordinates. Debug overlay, currently drawn directly in the scene render.