Commit Graph

3 Commits

Author SHA1 Message Date
566b870a5c Feed the mouse to the UI and tell the host which events it consumed
akgl_ui_handle_event is the mouse half of the input story -- keyboard and
gamepad stay with the control maps, because keyboard focus is something an
application declares rather than something a pointer position implies. It
handles motion, left-button presses and releases, the wheel and window
resizes; everything else, and everything while the subsystem is inert,
reports consumed=false and succeeds, the same pass-everything contract
akgl_controller_handle_event already has. The documented call order is UI
first, control maps second, skipping on consumed.

A press, release or wheel is consumed when the pointer is over any UI
element. The hit test asks clay against the layout the previous frame
retained -- this frame's does not exist while events are polled, and one
frame of staleness is the standard model's accepted cost -- with clay's
internal full-screen Clay__RootContainer excluded, because being inside the
window is not being over the interface (unexcluded, every click anywhere was
consumed; the test that pins this caught it).

Pointer state is fed to clay per event, as clay documents; the press edge is
latched once per frame_begin for the widgets to come, rather than trusting
Clay_PointerData's per-SetPointerState edge, which advances per mouse event.
frame_begin also drives Clay_UpdateScrollContainers from the accumulated
wheel (32 pixels per notch) and a wall-clock dt.

Tests pin the whole contract: pass-through before init, no consumption
before any layout exists, press/release inside vs beside a panel, right
button ignored, motion never consumed, wheel consumed only over the UI,
resize reaching the layout engine, and key events left alone.

Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 11:10:35 -04:00
3b3bfcbe10 Render clay layouts: fonts, text measurement, executor and the frame bracket
The UI subsystem now draws. akgl_ui_frame_begin/frame_end bracket one frame's
CLAY() declarations: begin clears the error stash and starts the clay layout,
end computes it and walks the render commands through a backend -- RECTANGLE
as (rounded) fills, BORDER as radius-shortened edge fills plus corner arcs,
TEXT through akgl_text_rendertextat one wrapped line per command, IMAGE as an
akgl_Sprite's first frame stretched to the bounding box, and the SCISSOR pair
through akgl_draw_set_clip, cleared again on any exit so a failed frame
cannot leave the world clipped.

akgl_ui_font_register maps registry font names onto clay's uint16_t fontIds.
The table keeps the *name* and resolves it per use -- fonts are not reference
counted, and a cached TTF_Font* would dangle where a name reports "gone"
honestly. Clay_TextElementConfig.fontSize is deliberately ignored: libakgl
bakes the size into the handle at load, so one face at two sizes is two ids.

The measure bridge passes clay's non-NUL-terminated slices straight to
SDL_ttf's explicit-length TTF_GetStringSize -- no copy, no scratch. Its
signature has no error channel, so failures report zero-by-zero and stash a
message that frame_end raises, the same route clay's own void error handler
uses; layout errors take precedence over drawing and one bad frame leaves the
next one clean.

Tests drive real CLAY() layouts through the software renderer and read pixels
back: fill placement, border edges with an empty middle, a child clipped by
its container, text landing in its colour, slice-vs-whole measurement
agreement, fontId table dedupe/refusal/exhaustion, and the bracket's
begin/begin, end-without-begin and failure-recovery contracts.

Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 11:06:27 -04:00
b7ff54b09f Vendor clay and bring the UI subsystem up: arena, lifecycle, status band
clay v0.14 (deps/clay, zlib licence) supplies the layout engine for the new
akgl_ui subsystem. Sources-listed rather than add_subdirectory()'d, on the
semver/libccd precedent: clay's CMakeLists declares no library target, builds
its examples by default, and requires CMake 3.27 against this project's 3.10.
src/ui_clay.c is the one CLAY_IMPLEMENTATION translation unit, compiled -w on
the vendored-code terms but with clay's symbols deliberately exported --
consumers' CLAY() macros resolve against libakgl.so, and akgl/ui.h warns them
never to link a second clay.

The subsystem is runtime-optional the way collision is: always compiled in,
inert until akgl_ui_init(), which bounds clay to the overridable AKGL_UI_*
ceilings, checks Clay_MinMemorySize() against a static BSS arena (no malloc),
and refuses with both numbers in the message when it does not fit. Measured:
812544 bytes at the default 1024 elements / 4096 measured words, against a
1 MiB arena. clay's void-callback layout errors are logged as they happen and
the first is stashed for the error protocol to raise later.

AKGL_ERR_UI joins the status band before AKGL_ERR_LIMIT, named in
akgl_error_init. New `ui` test suite covers validation, the init/shutdown
lifecycle, and arena exhaustion via the akgl_ui_arena_limit test hook (same
contract as akgl_ccd_arena_set_limit). clay.h is installed beside semver.h,
its licence beside libccd's, and the headers suite proves akgl/ui.h
self-contained -- clay.h compiles clean under -Wall.

Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 10:53:11 -04:00