Commit Graph
23 Commits
Author SHA1 Message Date
iris-ai c8beca5753 Give the text example's aligned labels the width to align in
All three sat in the middle of a box the width of the widest of them, so
left, centred and right were the same picture. `text_align` puts the glyphs
somewhere in the box the text is given, and a text that reports the width of
its own glyphs is given exactly that -- there is nowhere for it to sit.
Declaring `rel(1.0)` on each hands it the row instead. (Bryan, 2026-09-17.)
2026-09-17 03:21:17 -04:00
iris-aiandClaude Opus 5 d21a21524f Rename WidgetPtr to Wrapper and give it a builder
Bryan's call, 2026-09-16: a length and an alignment are properties of one
widget, so a widget cannot both be 100 wide and take two shares of a row --
that needs two widgets, and the second one should do as little as possible.
`WidgetPtr` already was that widget: it draws its child in the whole of its
box and reports what the child said. It only lacked a name that says so and
a way to make one around an existing widget.

`Wrapper` rather than `Wrap` so it cannot be read as the text setting, and
`.wrapper()` rather than `.wrapped()` for the same reason. Its child stays
optional, since being a swappable slot is what it was written for and what
the tab bar still uses it as.

`set_ptr` is deleted rather than renamed. It had no caller, and putting a
widget into an existing wrapper is what `Wrapper::set` already does.

`tabs` draws its centred square again: `.sized((100, 100)).center()
.wrapper().width(leftover(2))` is two widgets where the chain without
`.wrapper()` was one, and `.width` was overwriting what `.sized` set. That
was the last of the three ways `tabs` had drifted from canonical `main`
unnoticed; what is left between them is the truncated multiply's antialiased
edges and the widget count itself.

`widget_trait!` takes no attributes, so `.wrapper()` carries an ordinary
comment and the explanation lives on `Wrapper`.

Checked: fmt, clippy, 83 suite tests, 17 core unit tests, the release oracle
at 100 seeds, and `tabs` rendered at 1920x1200 against `main`'s own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 20:48:07 -04:00
iris-aiandClaude Opus 5 691e3eb23c Rename rest to leftover
The length kind that asks for a part of what is left once the fixed
lengths are taken is called leftover: Len::leftover(2), Len::LEFTOVER,
Size::LEFTOVER, Len::leftover the field, and apply_leftover. It says
what it is where "rest" reads as "the remainder of the list" as often as
"the remaining space", and every agent who has touched this has reached
for a third word for it.

Locals called rest that meant a region or a widget are renamed with it,
since the word now names something else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 14:22:52 -04:00
iris-aiandClaude Opus 5 4178dfbff9 Grow padding into the random trees, and take children in and out of spans
Padding as a node, with each of the four sides its own number: a padding that
is the same all round hides anything that treats one edge differently from
another. Spans now hold two to four children, so a pattern of removals has
something to make a pattern out of.

Five ways of changing what a span holds, each a shape worth its own case
rather than one shuffle: every other child out, everything but the first out,
three on at once, the first out and three on, and one out of the middle with
one on the end. Each is applied to every third span, and the cold tree is
grown holding exactly what the warm one was left with.

Three spare leaves are grown beside every span whether they end up in it or
not, so a tree that leaves them out makes the same widgets in the same order
as one that puts them in -- otherwise the two trees' `ids` stop lining up at
the first difference and every comparison after it is against the wrong
widget. Attaching one moves it, since a widget belongs to one parent;
`upgrade` is for a weak handle that was never added, not a second share. The
detached children are held until the comparison is over for the same reason:
dropping the last share of one frees its id for the next widget to be given.

Each case asserts the tree actually changed before comparing, so a shuffle
that quietly did nothing fails rather than passes.

All of it agrees: 49 tests, and the ignored sweep over 100 seeds and eight
scenarios, 800 comparisons.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:48:42 -04:00
iris-aiandClaude Opus 5 86a7e8dfc3 Grow random trees, and check them against building the same tree cold
`iris::random` grows a seeded tree -- spans in every direction, stacks,
rects with varying opacity, text both wrapping and overflowing, a declared
size over half of it -- and `tests/generated.rs` grows each seed twice: once
and then mutated, once with the mutation built in. Every widget's box has to
match. `examples/random.rs` draws one, and `IRIS_SEED`/`IRIS_DEPTH` pick it.

It found the defect in the commit before this one immediately: a reuse that
marked a descendant for redraw escalated to that descendant's size reader,
which re-placed the child, which marked it again. `try_reuse` now asks
whether anything under the widget would have to be drawn again *before*
keeping the drawing, and drops the whole thing if so, which terminates
because it adds no marks.

It also found one older and larger than this branch, which
`a_wrapping_child_of_a_row_settles_somewhere_else_each_time` reproduces and
documents: a wrapping text on a span's own axis is shaped twice against two
different widths, so where it settles depends on how many passes it has had.
7 of 90 cases diverge on `db1751f` and 30 do here, because a placed child
reaches the second shaping more often. It is the same defect either way, and
it belongs where the two draws meet -- LAYOUT.md §4 -- not in the chain. The
six seeds the live tests use are ones that agree.

`forget_ref` goes with the subtree rewrite that used it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:06:09 -04:00
iris-ai f9423855e1 Size a widget while drawing it, not in a pass of its own (#16)
Reviewed-on: iris/iris#16
Reviewed-by: iris <2+iris@noreply.localhost>
Co-authored-by: iris-ai <4+iris-ai@noreply.localhost>
2026-09-14 02:48:02 -04:00
iris-aiandiris c8ac669f95 Run a ui without a window, and test one (#15)
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>
2026-09-13 21:53:54 -04:00
iris-aiandiris b234497d21 Draw the glyph atlas as an array texture and images with their own bind groups + primitive rendering overhaul
Replaces the bindless `binding_array<texture_2d<f32>>` the renderer bound every texture through. That array needs `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack, so the old shape did not run there at all.

The two things being bound want opposite treatment, so they are now split:

- **Glyph atlas pages become layers of one `texture_2d_array`.** A glyph primitive carries a `layer` instead of a view/sampler index pair. A layer index is an ordinary sampling operand, so this needs nothing beyond plain Vulkan 1.0 / GLES. Growing the atlas recreates the array with headroom and `copy_texture_to_texture`s the old layers across, no readback.
- **A standalone image gets its own texture and its own bind group,** and draws in its own call. It no longer needs a per-instance entry in `PrimitiveData`: the bind group has already picked the texture.

`Primitives` keeps images in a list of their own as a result, with `PrimitiveChange::is_image` naming which list a renumbering belongs to -- the two have independent index spaces, so `(layer, inst_idx)` alone would collide between them.

Two notes on judgement calls, since this slice was rebuilt on top of `main` rather than transplanted:

- The source version renamed `GlyphEntry::is_colored` to `is_color` and added a second `IS_COLOR` flag constant beside the existing `GlyphEntry::IS_COLORED`. Both dropped: #10's naming and its `flags()` are kept, and UVs stay `Vec2` rather than going back to `[f32; 2]`.
- `ImageGpu` no longer holds the `Texture` behind its view, which removes an `#[allow(dead_code)]`. A `TextureView` keeps its own reference to the texture, checked by rendering rather than assumed -- see below.

### Verification

```
cargo fmt --all --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
```

All clean; the 4 text-edit tests pass. The only clippy output is the pre-existing future-incompatibility notice about `naga`/`wgpu`/`winit`.

Because this is a rendering change, it was also run for real rather than only compiled. The `tabs` example was rendered on this machine's GPU -- Venus onto an RX 7900 XT, confirmed from the loaded ICD (`libvulkan_virtio.so` on `/dev/dri/renderD128`) rather than assumed, since a failed Vulkan init here silently falls back to llvmpipe and would make the screenshots meaningless.

Screenshots before and after the change are **byte-identical** (same md5) in two scenes: the default tab, which exercises text (the atlas path) and rects, and the image tab with a standalone image pushed at startup, which exercises the per-image bind group. The image-tab scene needed a temporary local edit to the example to push the image without a click; that edit is not part of this branch. The same comparison, re-run after dropping the `Texture` field, is still byte-identical -- which is the check that the view alone keeps it alive.

---------

Co-authored-by: iris <2+iris@noreply.localhost>
Reviewed-on: iris/iris#11
Reviewed-by: iris <2+iris@noreply.localhost>
Co-authored-by: AIris <4+iris-ai@noreply.localhost>
2026-09-13 18:56:59 -04:00
iris-aiandiris 0f6a28b4dd Move text layout and rendering to Parley (#10)
Replace the cosmic-text path with Parley layout and Swash rasterization, backed by shared glyph-atlas pages. Shaping, editing, rasterization, and glyph rendering move together because they share the text buffer and rendered-glyph types; splitting them further would require a temporary renderer that is immediately removed.

This is reconstructed rather than replayed from the extraction history. It also fixes issues found during review:

- texture binding changes remain set when an atlas patch follows a new page
- pressing an empty field places a caret and accepts input
- selection motion delegates collapse behavior to Parley
- character deletion follows logical clusters rather than visual neighbors
- the unused root-level Swash dependency is omitted

Four public-behavior integration tests live in `tests/text_edit.rs`: empty-field input, multibyte IME preedit replacement, UTF-8-safe backspace, and selection replacement. The old twelve-test inline block and implementation-restating cases are omitted.

Every added source comment was manually reviewed. Comments that narrated implementation or history were removed; retained comments document cache/rasterization keys, GPU upload constraints, focus representation, bidi geometry, or IME semantics.

Known limitation: atlas pages currently grow without eviction. Each page is 4 MiB on CPU and GPU. An arbitrary cap would leave cached rendered-text UVs pointing at reused glyph slots, so bounding this safely needs a later generation/invalidation change.

This changes public text types and signatures. GPU glyph rendering is covered by compilation rather than a live-surface test.

Verified with:

- `cargo fmt --all --check`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `cargo test --workspace` (four integration tests pass)

Cargo still reports inherited future-incompatibility notices for existing wgpu/winit dependencies; there are no current clippy warnings.

---------

Co-authored-by: iris <2+iris@noreply.localhost>
Reviewed-on: iris/iris#10
Reviewed-by: iris <2+iris@noreply.localhost>
Co-authored-by: AIris <4+iris-ai@noreply.localhost>
2026-09-13 03:39:23 -04:00
iris 7bafb04a34 widget state 2026-01-19 20:39:58 -05:00
iris 06dd015092 finished moving out render_state 2026-01-19 18:00:24 -05:00
iris 79813db3ba work 2026-01-12 18:40:27 -05:00
iris d11107f965 widget view 2026-01-05 17:01:09 -05:00
iris f2ac6f195f remove typed stuff / just specify rsc if needed 2026-01-03 18:06:05 -05:00
iris 59901b6580 tasks initial impl (still working on task_on trait method) 2026-01-03 16:26:23 -05:00
iris 5da1e9e767 separate state from rsc 2026-01-01 22:18:08 -05:00
iris bae17235c6 better global state structure? 2025-12-19 21:54:48 -05:00
iris 8d1a810483 macro goodness 2025-12-15 23:11:32 -05:00
iris 0b8a93c5ce RE ADD CONTEXT 2025-12-15 21:50:53 -05:00
iris 2dc5b0f62c refactor project structure (start of redoing atomic branch without atomics) 2025-12-11 05:25:58 -05:00
iris 62aa02847a redo event fn signature & add event_ctx macro 2025-12-06 20:48:10 -05:00
iris 28d17c49c6 shift code 2025-12-04 14:54:40 -05:00
iris db888416b6 add minimal example 2025-12-04 14:46:34 -05:00