Add retained paints and shared text selection
This commit is contained in:
1 parent
25370731d0
commit
de92fccba5
60 files changed
+2848
-1297
No files matched your search
+13
-30
@@ -2,35 +2,10 @@
|
||||
|
||||
Only open Iris framework work lives here. Delete an item when it lands.
|
||||
|
||||
## Fix
|
||||
|
||||
- [ ] **Colours are not in a defined colour space. Fix this before a
|
||||
styling pass.** Both render backends prefer an sRGB surface
|
||||
(`default/render.rs` and `android/render.rs`), while `fs_main` returns
|
||||
`unpack4x8unorm` palette and image bytes unchanged. An sRGB attachment
|
||||
treats those values as linear and encodes them again: Mocha Crust
|
||||
(17,17,27) became (73,73,91) in a desktop screenshot measured on
|
||||
2026-09-06.
|
||||
|
||||
Neither diagnostics nor startup logging records Android's selected surface
|
||||
format. A device exposing only a non-sRGB surface can hide the shared bug.
|
||||
|
||||
Done means defining one convention for palette bytes, decoded images,
|
||||
colour emoji and the clear colour, then converting exactly once for the
|
||||
selected target. Record the selected format in diagnostics, and add a GPU
|
||||
test that draws known non-black, non-white pixels into an sRGB target and
|
||||
reads the stored bytes back; screenshots from desktop and Android then
|
||||
confirm the same Catppuccin values rather than serving as the definition.
|
||||
|
||||
## Build (for the port)
|
||||
|
||||
Framework capabilities needed by `RUST.md`'s port plan:
|
||||
|
||||
- [ ] **Selectable, read-only text.** P0's report and P1's transcript rows
|
||||
use `TextEdit` because `Selectable` is implemented only for it. That
|
||||
makes prose focusable and opens the IME over text that cannot be edited.
|
||||
A display widget needs the same selection geometry and clipboard path
|
||||
without a text-input accessibility role or keyboard focus.
|
||||
- [ ] **Overflow ellipsis with an explicit retained end.** `TextAttrs` can
|
||||
only wrap or clip, so a tool summary is cut with no mark. Parley has no
|
||||
ellipsis primitive; use its line breaker to find the cut, but keep source
|
||||
@@ -52,9 +27,9 @@ Framework capabilities needed by `RUST.md`'s port plan:
|
||||
- [ ] **Per-range backgrounds for rich text.** (**P1**.) Inline code is
|
||||
already monospace and coloured, but matching Compose's chip also needs
|
||||
the glyph run's boxes so a surface can be drawn behind exactly that byte
|
||||
range. `TextEdit` already computes the same geometry internally for its
|
||||
selection highlight; expose one shared primitive rather than giving the
|
||||
app a second text-layout path.
|
||||
range. The shared `TextSelection` engine already computes the same geometry
|
||||
for selection highlights; expose one shared primitive rather than giving
|
||||
the app a second text-layout path.
|
||||
- [ ] **A modal/dialog primitive.** (**P1**, reused by **P3** and
|
||||
**P5**.) Needed for the session settings dialog, `UsageDialog`'s
|
||||
equivalent, and the delete-with-`deleteForeign` confirmation with its
|
||||
@@ -69,6 +44,14 @@ Framework capabilities needed by `RUST.md`'s port plan:
|
||||
|
||||
## Later
|
||||
|
||||
- [ ] **Intern independently constructed solid paint definitions.** Inline
|
||||
`rect(Srgba8::...)` values currently receive a new `PaintId` each time.
|
||||
Cache them by canonical linear RGBA bits, but keep `Paints::add` explicitly
|
||||
unique so two semantic theme roles that start with the same value can later
|
||||
change independently. Cache entries must be weak and disappear when the
|
||||
last real handle releases the slot; gradients and texture paints need their
|
||||
own identity rules rather than inheriting solid-value interning blindly.
|
||||
|
||||
- [ ] **Property/content animations.** Cosmetic, so after correctness and
|
||||
parity. Keep them modular, like input; scrolling already animates through
|
||||
`Widget::tick` and `UiData::animate`. A widget that does not opt in must
|
||||
@@ -85,8 +68,8 @@ Framework capabilities needed by `RUST.md`'s port plan:
|
||||
against `.masked_by(rect(BAR_FILL))` on the composer at the phone's own
|
||||
size and density and the two are identical to the pixel. What the pair
|
||||
cannot express is a clip that is not a box: `Painter::set_mask` writes
|
||||
a `RectPrimitive::color(Color::NONE)` at the widget's own region, with
|
||||
no radius, so `.background(rect(fill).radius(r)).masked()` draws a
|
||||
a `RectPrimitive::color` using `PaintId::NONE` at the widget's own region,
|
||||
with no radius, so `.background(rect(fill).radius(r)).masked()` draws a
|
||||
rounded panel and then cuts its content square. Both other call sites
|
||||
(`row.rs`'s fence, `tool.rs`'s raw output) are rounded, which is why
|
||||
the method stands for now.
|
||||
|
||||
+11
-25
@@ -22,8 +22,7 @@ the command and measured value when evidence matters.
|
||||
next session should start. Its box below has the state.
|
||||
- The port is one crate under `app-rust/`; `iris/` is only the UI framework.
|
||||
- **Open across the rest of the docs**: `docs/IRIS_TODO.md` is iris's own
|
||||
list (colour-space correctness is the live one), `docs/TODO.md` is the
|
||||
Compose app's.
|
||||
list; `docs/TODO.md` is the Compose app's.
|
||||
|
||||
## Desktop and phone share the code
|
||||
|
||||
@@ -81,7 +80,7 @@ runs inside `cargo test`.
|
||||
pan, and (e) the composer clears a simulated 1000px IME inset
|
||||
(`Composer::set_bottom_inset`). Each was confirmed to fail without
|
||||
its subject rather than assumed: dropping `animate(id)` from
|
||||
`Selection::drag` -- the phone's own "fling does nothing" defect --
|
||||
`SelectionController::drag` -- the phone's own "fling does nothing" defect --
|
||||
and starting the fling curve at the wall clock each fail only the
|
||||
flick test; flinging on `Tapped` fails only the tap test; a 5s
|
||||
`LONG_PRESS` fails only the selection test; a `set_bottom_inset` that
|
||||
@@ -269,11 +268,14 @@ already got right (v2 signing, `uses-feature`, ABI splits).
|
||||
Reading the Compose code for what a replacement must be able to express,
|
||||
rather than what it happens to look like:
|
||||
|
||||
1. **The transcript is one selectable body of text.** One
|
||||
`SelectionContainer` around the whole lazy list, so a selection runs from
|
||||
a reply into the tool output beneath it. The framework needs selectable
|
||||
read-only rich text across many rows, with the platform's selection
|
||||
handles and clipboard on the phone.
|
||||
1. **The transcript is one selectable body of text.** A
|
||||
`SelectionController` is registered directly on the lazy list, with no
|
||||
selection widget in the layout tree, so a selection runs from a reply into
|
||||
the tool output beneath it. Each parent supplies either draw order or one
|
||||
visual axis for its immediate children; ordering is resolved only when
|
||||
selection queries it. Iris owns the selection handles because its text is
|
||||
drawn into one surface, while the platform supplies the clipboard and
|
||||
related system services.
|
||||
2. **Rich inline text**: markdown with links (one tap detector per text,
|
||||
not a node per link), inline code chips drawn behind the text, tables
|
||||
with wrapping cells and a sideways scroll, syntax-highlighted fences,
|
||||
@@ -442,7 +444,7 @@ buffer on **every keystroke**. So there is nothing to adopt, and adding
|
||||
it would be upstream work in parley.
|
||||
|
||||
**And the app already does the thing incremental layout would buy.**
|
||||
`RowBlocks::apply_delta` keeps one `TextEdit` per top-level markdown
|
||||
`RowBlocks::apply_delta` keeps one `Text` per top-level markdown
|
||||
block and re-shapes only the block a delta landed in; re-splitting the
|
||||
markdown to find that block is 18µs at 18,000 characters and comparing
|
||||
the blocks is 470ns. Neither is the cost.
|
||||
@@ -664,12 +666,6 @@ pane is neither masked nor scrollable despite its construction comment saying
|
||||
it is both. This is an `app-rust` defect, not an iris framework item.
|
||||
|
||||
- [ ] **P1 — session screen parity.** Continue in this order:
|
||||
- [ ] **Before the next parity slice — make iris's colour pipeline
|
||||
correct.** Both backends currently prefer an sRGB surface while
|
||||
the shader returns palette/image bytes as
|
||||
linear values; `IRIS_TODO.md` has the measured mismatch and
|
||||
pass condition. Do this before judging or centralising the
|
||||
app's styling. It is correctness, not cosmetic polish.
|
||||
- [ ] **P1c — history paging and jump-to-latest.** Wire
|
||||
`client::transcript_source` into `src/ui`:
|
||||
the opening page, paging back on scroll with the cushion
|
||||
@@ -725,16 +721,6 @@ it is both. This is an `app-rust` defect, not an iris framework item.
|
||||
names are app content applied through iris's existing `.label()` API,
|
||||
not a missing framework widget.
|
||||
|
||||
**`app-rust` UI defect still open**: tool-card text is not selectable.
|
||||
A `TranscriptRow::Tools` has no markdown blocks, so cards can number
|
||||
their own texts from zero without colliding with the row selection
|
||||
keys. The risk is lifecycle: every card rebuild path must unregister
|
||||
the old `TextEdit` handles before registering replacements, or the next
|
||||
long press can find a freed handle. Cover result arrival, group toggle,
|
||||
a call joining a run and the per-card widget swap with stale-handle
|
||||
tests. This uses iris's existing selection API; it is not a framework
|
||||
widget gap.
|
||||
|
||||
**Pass condition**: `app/ui-sandbox.sh`'s fixtures driven by
|
||||
`ui-trace record --do "tap '<label>'"` — a session with the big
|
||||
transcript (`AI_SANDBOX_BIG_MB`), a paused/slow-spawning one
|
||||
|
||||
+5
-5
@@ -240,16 +240,16 @@ without a scroll-widget special case.
|
||||
The transcript registers the wheel **by hand rather than calling
|
||||
`LazySpan::scrollable()`**, and this is not an oversight. That helper also
|
||||
registers a finger drag driving the span's own `DragGesture`, and the
|
||||
transcript already has an arbiter — `Selection`, which must decide between
|
||||
transcript already has an arbiter — `SelectionController`, which must decide between
|
||||
panning and selecting text and so cannot let a second `DragGesture` see
|
||||
the same frames. `DragGesture`'s doc states the rule: one gesture, one
|
||||
arbiter, each frame delivered exactly once. The wheel handler registered
|
||||
here is identical to the helper's; only the drag differs.
|
||||
|
||||
`Selection` is given the span by `set_scroll_area` after it exists (rows
|
||||
need a `Selection`, and the span needs the rows), and hands it committed
|
||||
pans and releases through `Scrollable::scroll`/`fling`. There is no
|
||||
wrapper widget: `TranscriptScreen::list` is the layout (`extent`,
|
||||
`SelectionController` is attached directly to the span and holds its weak
|
||||
handle, then hands committed pans and releases through
|
||||
`Scrollable::scroll`/`fling`. There is no wrapper widget:
|
||||
`TranscriptScreen::list` is the layout (`extent`,
|
||||
`key_at`, `jump_to_end`) *and* the position (`amt`, `fling`,
|
||||
`is_scrolling`).
|
||||
|
||||
|
||||
@@ -42,6 +42,26 @@ adjacency after a free.
|
||||
Standalone images currently use `NonFiltering` sampling. Thumbnail scaling
|
||||
and filtering remain image-widget decisions, not texture-storage decisions.
|
||||
|
||||
## Colour convention
|
||||
|
||||
Palette literals and decoded image bytes enter Iris as straight-alpha sRGB.
|
||||
Solid paints are converted once when registered and stored in a separate GPU
|
||||
paint table as linear `vec4<f32>`; rect and glyph primitives carry only the
|
||||
paint-table index. `Paints::set` rewrites a slot in place, so a shared theme
|
||||
can change without rebuilding widgets or primitive buffers. A rect may also
|
||||
hold a `Paint` definition and resolve it to a `PaintId` on its first draw;
|
||||
independently constructed definitions deliberately receive independent slots.
|
||||
|
||||
Standalone images, colour glyphs, and atlas pages use
|
||||
`Rgba8UnormSrgb`, which makes sampling decode their RGB channels to linear
|
||||
light. Shaders therefore always return linear values. Both window backends
|
||||
render through an sRGB texture view in `SurfaceColorSpace::Srgb`, which
|
||||
encodes exactly once on output; the clear colour is linear too. A surface
|
||||
without an advertised RGBA/BGRA sRGB view and sRGB output space is rejected
|
||||
rather than silently displaying a different colour pipeline. The readback
|
||||
test in `iris/tests/color_space.rs` guards the solid, image, and in-place
|
||||
theme-update paths end to end.
|
||||
|
||||
## Verification rig
|
||||
|
||||
`scripts/rigs/gpu-probe` requests Iris's exact feature and limit set without a
|
||||
|
||||
Reference in new issue
Block a user