Files
ai-app/IRIS.md
T
irisandClaude Sonnet 1a6599e1b2 iris: Widget::draw reports the size it used, replacing desired_width/height
Implements LAYOUT.md end to end: one fn draw(&mut self, &mut Painter) ->
Size replaces draw + desired_width/desired_height on every widget in
iris/src/widget/, SizeCtx and Cache are deleted, and a moved widget
(Scroll, Offset) costs one move_offsets write resolved by a shared
resolve_move WGSL function in both shader stages -- O(1) regardless of
how many primitives are in its subtree, measured at 500 in the new
iris/src/layout_tests.rs (a plain unit test: UiRenderState touches no
GPU or window).

Five real bugs surfaced only by diffing iris/run-headless.sh screenshots
against the pre-change tree and are written up in LAYOUT.md's
"Deviations found during implementation": Aligned's provisional draw
composing painter.region() a second time through widget_within; Sized/
MaxSize reporting a capped size while still painting their child
unconstrained (fine under the old two-pass model, wrong once a parent
like Aligned draws before knowing the final size); a widget's
move_offsets parent link being unreadable from self.active while its
own ActiveData is still mid-construction; Painter::reposition needing
the child's *painted* footprint (its reported size, top-left anchored)
rather than its offered region; and a widget's move slot needing to be
reused in place across redraws, with its delta reset, rather than
reallocated.

All four iris/examples render pixel-identical to the pre-change tree.
cargo fmt/clippy/test clean across the workspace (18 tests: 14
pre-existing plus 4 new).

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
2026-09-04 23:40:56 -04:00

4.3 KiB

iris: notable public API changes

For Iris to read on her own time. Each entry is a change to iris's public surface that a widget author or app author would notice: a trait method added, removed or re-shaped; a type that callers construct differently; a capability that moved. Small and trivial changes do not go here.

An entry gives the date, what changed, why, and a short before/after where it helps judge the change without the session that made it. Newest first.

2026-09-04: Widget::draw reports the size it used; desired_width/desired_height are gone

A widget used to implement three methods (draw, desired_width, desired_height); it now implements one, fn draw(&mut self, painter: &mut Painter) -> Size, which draws into painter.region() and returns how much of it was used. Why: the two extra methods routinely re-simulated what draw was about to do anyway (Span::desired_ortho copied its own draw loop to get cross-axis sizing right) — one visit per widget per frame instead of up to three. A container that needs a child's size before placing it (alignment, centering) draws the child once at a provisional region, reads the returned Size, and calls the new Painter::reposition to move it into its final spot — an O(1) offset write, not a second draw. A widget whose drawn output never depends on the size it's given (a fixed-size Rect, a decoded Image) overrides the new fn is_size_independent(&self) -> bool { false } to true, which skips redrawing it when only its offered region changes shape.

// before
fn draw(&mut self, painter: &mut Painter) { /* ... */ }
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { /* ... */ }
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { /* ... */ }

// after
fn draw(&mut self, painter: &mut Painter) -> Size { /* ... */ }

SizeCtx and Cache are gone with it — see LAYOUT.md for the full design, the move-offset mechanism this shipped alongside, and the file list.

2026-09-04: texture pipeline rebuilt off the binding array

Textures/TextureHandle, GlyphPrimitive, and UiRenderNode::new all changed shape. Why: the old pipeline bound every texture ever drawn in one binding_array<texture_2d<f32>> and asked every device, unconditionally, for VK_EXT_descriptor_indexing — a real share of Android GPUs lack it, and it failed outright on the Android emulator's software Vulkan. See TEXTURES.md's "Recommended shape" and "Implemented, 2026-09-04".

  • UiRenderNode::new drops its limits: UiLimits parameter, and UiLimits is gone. Before: UiRenderNode::new(&device, &queue, &config, UiLimits::default()). After: UiRenderNode::new(&device, &queue, &config). Nothing replaces it — there are no more binding-array limits to size.
  • src/default/render.rs's device request asks for no features and no binding-array limits. Before: required_features: Features::TEXTURE_BINDING_ARRAY | Features::PARTIALLY_BOUND_BINDING_ARRAY | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING plus two max_binding_array_* limits. After: Features::empty() (the DeviceDescriptor default) and only max_buffer_size set, which was never about the binding array.
  • TextureHandle has no primitive() method any more; a caller outside iris shouldn't have been calling it (it fed the old renderer's internals), but if something did: use image_index() for a standalone image's bind-group index. There is no equivalent for a page — a page has no bind group of its own now, see below.
  • GlyphPrimitive has no public constructor from a struct literal. Before: GlyphPrimitive { uv_min, uv_max, view_idx, sampler_idx, color, flags }. After: GlyphPrimitive::new(uv_min, uv_max, layer, color, flags) — one layer (the shared atlas array's layer) instead of a view_idx/sampler_idx pair, since a page is now a layer of one array texture rather than its own bound texture.
  • A widget author drawing images is unaffected: Painter::texture/ texture_at/texture_within and Textures::add keep their signatures. What changed underneath is that each standalone image now gets its own wgpu::BindGroup and draw call instead of a slot in the shared array — invisible from the widget API, visible only in UiRenderNode's internals and in iris's device requirements.