Skip to content

ui: contain widget drawing — clip stack, render() widget clip, gfx_read pixel oracle (#823) - #860

Merged
InauguralPhysicist merged 1 commit into
mainfrom
ui-containment-823
Aug 5, 2026
Merged

ui: contain widget drawing — clip stack, render() widget clip, gfx_read pixel oracle (#823)#860
InauguralPhysicist merged 1 commit into
mainfrom
ui-containment-823

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Closes #823.

Two faces of one gap (F-DYN-12): widgets did not contain their own drawing — a canvas on_paint could paint over surrounding chrome, and a label wider than its column drew past the panel and off the window, invisible to the stubbed suite (the #599 class).

What changed

  • ui_clip_push / ui_clip_pop (lib/ui_draw.eigs) — a nesting clip stack over gfx_clip: push INTERSECTS with the current clip, pop restores the parent clip instead of clearing it. Exported for custom paint routines.
  • render() clips every widget to its own rect intersected with its ancestors' — canvas on_paint included, and label overflow is now defined: crop at the container edge (size deliberately with text_width). Registry opt-out "clip": 0 for the four renders that legitimately leave the rect: dropdown/combobox open lists, the floating menu, dialog's full-screen dim, grid's documented row-label gutter. Reducing those to menu+dialog (overlay-pass migration per lib/ui: no menu-bar widget — desktop-app shells must hand-roll one from buttons + show_menu #565, gutter folded into the rect) is lib/ui: move dropdown/combobox open lists to the #565 overlay pass; fold grid's row-label gutter into its rect #859.
  • All raw gfx_clip set/clear pairs in lib/ui migrated to the stack — a child's gfx_clip of null used to wipe its parent's clip.
  • gfx_read of [x, y][r, g, b] (src/ext_gfx.c): back-buffer pixel readback via SDL_RenderReadPixels — the render-decode oracle primitive. Nondeterministic input, so it takes the TRACE_NONDET_TAKE/RECORD tape pair like audio_stream_queued. Registered through ext_names.h (linter E003 base and LSP index pick it up by construction).

Proof, both halves

  • Stubbed [63] (+14): containment proved on RECORDED clip state — nested push intersects, pop restores the parent (not null), canvas paint records under exactly its rect, an overflowing label's clip ends at the panel edge. Planted fault: re-registering canvas with "clip": 0 makes the probe report no active clip, then restoring restores containment.
  • Real pixels, new [132] (tests/test_ui_containment_gfx.eigs): the actual SDL software renderer under SDL_VIDEODRIVER=dummy draws an escaping on_paint and an overflowing label; gfx_read decodes the back buffer — escape pixels must not exist. Its own planted fault flips the probe red. Runs in the CI gfx leg with no display.

Validation

  • Full suite vs gfx build: 3808/3808, twice.
  • make asan + ASAN_OPTIONS=detect_leaks=1: 3756/3756, leak tally 0.
  • --lint on all touched lib/tests files: no new warnings.
  • docs: BUILTINS.md, STDLIB.md (clip stack + label overflow contract), CHANGELOG.

Follow-up filed: #859 (move open lists to the #565 overlay pass; fold grid gutter into its rect).

Closes #823.

🤖 Generated with Claude Code

…ad pixel oracle (#823)

Closes #823.

- lib/ui_draw.eigs: ui_clip_push/ui_clip_pop — a nesting clip stack over
  gfx_clip; push INTERSECTS with the current clip, pop restores the
  parent clip instead of clearing it.
- lib/ui.eigs render(): every widget draws under a clip of its own rect
  intersected with its ancestors' — canvas on_paint included (F-DYN-12),
  and label overflow is now defined: crop at the container edge.
  Registry opt-out ("clip": 0) for dropdown/combobox open lists, menu,
  dialog's dim overlay, grid's row-label gutter (follow-up: #859).
- All raw gfx_clip set/clear pairs in lib/ui migrated to the stack (a
  child's clear used to wipe its parent's clip).
- src/ext_gfx.c: gfx_read of [x, y] -> [r, g, b] back-buffer readback,
  the render-decode oracle primitive; nondet input, TAKE/RECORD tape
  pair like audio_stream_queued.
- tests: [63] +14 stubbed containment checks (recorded clip state,
  planted fault via registry opt-out); new [132] real-pixel oracle
  (tests/test_ui_containment_gfx.eigs) under SDL_VIDEODRIVER=dummy with
  its own planted fault.
- docs: BUILTINS.md gfx_read row, STDLIB.md clip-stack + label-overflow
  contract, CHANGELOG; LSP builtin+stdlib indexes regenerated.

Validated: full suite green against gfx build twice (3808/3808) and
under ASan+UBSan with detect_leaks=1 (3756/3756, leak tally 0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 10:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses UI containment gaps by ensuring widget rendering is clipped to each widget’s rectangle (including canvas on_paint) and by adding a real-renderer pixel readback builtin (gfx_read) to enable render-decode “pixel oracle” tests under the SDL dummy driver.

Changes:

  • Added a nesting clip stack (ui_clip_push/ui_clip_pop) and updated render() to clip each widget to its rect unless the widget registry opts out ("clip": 0).
  • Migrated existing lib/ui internal clipping sites from raw gfx_clip set/clear pairs to the new clip stack, preventing child clears from wiping ancestor clips.
  • Introduced gfx_read of [x, y] -> [r, g, b] and added both stubbed and real-pixel containment tests, with runner gating for gfx builds.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/test_ui.eigs Adds stubbed-gfx containment assertions by inspecting recorded clip state (with planted-fault validation).
tests/test_ui_containment_gfx.eigs New real-renderer pixel-oracle test using gfx_read under SDL_VIDEODRIVER=dummy (with planted fault).
tests/run_all_tests.sh Adds probe-gated section [132] to run the real-pixel containment oracle only when gfx is available.
src/ext_names.h Registers the new gfx_read builtin in the ext name table.
src/ext_gfx.c Implements builtin_gfx_read using SDL_RenderReadPixels with trace nondet TAKE/RECORD.
lib/ui.eigs Updates render() to wrap widget renders in a clip push/pop (with registry opt-out).
lib/ui_w_viz.eigs Replaces raw gfx_clip usage with ui_clip_push/ui_clip_pop in viz widgets.
lib/ui_w_special.eigs Migrates splitter rendering clips to the clip stack.
lib/ui_w_menu.eigs Marks dropdown and menu registry entries as "clip": 0 to allow out-of-rect rendering.
lib/ui_w_input.eigs Marks combobox registry entry as "clip": 0 for its open list rendering.
lib/ui_w_dock.eigs Migrates dock child clipping to the clip stack.
lib/ui_w_dialog.eigs Marks dialog as "clip": 0 for fullscreen dim overlay rendering.
lib/ui_w_data.eigs Migrates table/tree/item_list/hex_view clips to stack; marks grid "clip": 0 due to documented gutter behavior.
lib/ui_w_container.eigs Migrates scroll_panel clipping to stack.
lib/ui_draw.eigs Introduces the ui_clip_push/ui_clip_pop stack implementation over gfx_clip.
docs/STDLIB.md Documents the clip stack API and the defined label overflow/containment behavior.
docs/BUILTINS.md Documents the new gfx_read builtin, including back-buffer timing and trace behavior.
CHANGELOG.md Adds release notes for gfx_read, clip stack, and contained widget drawing behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@InauguralPhysicist
InauguralPhysicist merged commit 078e759 into main Aug 5, 2026
19 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the ui-containment-823 branch August 5, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lib/ui: canvas does not clip its own on_paint, and panel labels overflow their column instead of being sized/clipped

2 participants