`try_reuse`'s pure-translation case now writes the widget's move slot
instead of remapping every primitive in its subtree. Counted on a span
of 20 rows, each five primitives deep, when the row above them changes
height:
before 100 primitive region writes
now 0, and 20 slot writes -- one per row the span re-placed
`window_region` walks the same chain on the CPU, so hit testing and
anyone asking in window pixels see a widget where the shader draws it.
`Moves::resolve` stops at `CHAIN_LIMIT` like the shader, and asserts in
debug that it got to the end rather than running out.
Two things fall out of it. Rewriting a region is the one thing a slot
cannot express, so `mov` zeroes the slots of everything it rewrites: a
region is what its slot was a delta from. And `try_reuse` loses its
`old == region` shortcut, which was wrong once a slot exists -- a widget
offered exactly the box it drew against has to have its delta cleared,
not skipped.
`Moves` lives on `UiRenderState` rather than `UiData`, because the draw
is what produces it and `window_region` should not need the ui's
resources to answer where something is. The renderer already takes both.
A slot is retired in `remove_rec`, after the descendants whose slots
name it as their parent. Either order is correct here -- nothing can
claim a freed index while a subtree is coming down, since `on_undraw`
cannot reach the slots -- but this way `remove`'s `undraw` flag only
notifies rather than also deciding slot lifetime, and the retirement
sits beside the recursion it follows.
Checked: fmt, clippy and 41 tests. `tabs` (with the image replay),
`view`, `minimal` and `text` all still render byte-identical, and the
live sway resize round trip -- which re-places most of the tree at the
same size, so it is the slot path throughout -- matches a cold start at
each size.
Small, and disjoint from #12 — this touches `task.rs`, `harness.rs` and `render_state.rs`, none of which #12 goes near.
`Tasks` held an `Arc<Window>` only to call `request_redraw` when a task finished, which made the task queue, and so `DefaultRsc`, impossible to build without a window. It now takes an `Arc<dyn WakeTaskQueue>`, and `Window` implements it.
Waking also moves from *the task ended* to *an update was sent*, which is when there is actually something for the host to apply. A task that keeps running after sending one no longer holds it until it finishes, and a task that sends none no longer asks for a frame nothing needs.
`iris::harness` is what that buys. `UiRenderState` already does layout, hit testing and primitive building with no surface, so a test can build a tree, run frames, move a pointer and read back where widgets landed. `tests/harness.rs` covers span layout, resize relayout, press routing, hover start and end, wheel scrolling with its clamp, and a task update reaching the tree. None of them could be written before, since the only way into layout was a window.
It does not draw. A claim about pixels still needs a real surface — I checked this one against the rig rather than asserting it: `examples/task` under headless sway, centre pixel `ff0000` before the click and `0000ff` after, so the windowed path still applies task updates under the new wake.
The only core change is `UiRenderState::output_size()`, so that a host reading back the size it set does not have to keep a second copy.
---------
Co-authored-by: iris <2+iris@noreply.localhost>
Reviewed-on: iris/iris#15
Reviewed-by: iris <2+iris@noreply.localhost>
Co-authored-by: AIris <4+iris-ai@noreply.localhost>