From 599d33287c8d91f879a553eccdc07f51ea706e4f Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Thu, 10 Sep 2026 18:49:03 -0400 Subject: [PATCH] Add scoped overlay hosts --- app-rust/examples/phone.rs | 2 +- app-rust/src/android/bench_client.rs | 2 +- app-rust/src/android/transcript_client.rs | 2 +- app-rust/src/ui/fixture.rs | 7 +- app-rust/src/ui/mod.rs | 11 +- app-rust/src/ui/row.rs | 7 +- app-rust/src/ui/tap.rs | 7 +- app-rust/tests/top_edge.rs | 2 +- docs/IRIS_TODO.md | 5 - docs/RUST.md | 16 +- iris/core/src/event/controller.rs | 30 + iris/core/src/event/rsc.rs | 13 +- iris/core/src/widget/like.rs | 8 +- iris/examples/bench_images.rs | 2 +- iris/examples/message_list.rs | 2 +- iris/src/android/ime.rs | 4 +- iris/src/android/view.rs | 13 +- iris/src/default/mod.rs | 11 +- iris/src/harness.rs | 6 +- iris/src/lib.rs | 2 + iris/src/overlay.rs | 812 ++++++++++++++++++++++ iris/src/widget/text/selection.rs | 2 +- iris/tabs-ui/src/lib.rs | 2 +- iris/tests/color_space.rs | 2 +- 24 files changed, 910 insertions(+), 60 deletions(-) create mode 100644 iris/src/overlay.rs diff --git a/app-rust/examples/phone.rs b/app-rust/examples/phone.rs index 554a8cf..9985c9e 100644 --- a/app-rust/examples/phone.rs +++ b/app-rust/examples/phone.rs @@ -93,7 +93,7 @@ impl DefaultAppState for Client { .pad(dp(16)) .add_strong(rsc) .any(); - ui_state.set_root(text); + ui_state.set_root(rsc, text); None } }; diff --git a/app-rust/src/android/bench_client.rs b/app-rust/src/android/bench_client.rs index dd2d72a..642fe22 100644 --- a/app-rust/src/android/bench_client.rs +++ b/app-rust/src/android/bench_client.rs @@ -142,7 +142,7 @@ impl AndroidAppState for BenchClient { .span(Dir::DOWN) .add_strong(rsc) .any(); - ui_state.set_root(tree); + ui_state.set_root(rsc, tree); let font = rsc.ui.text.font_diagnostics(); log::info!( diff --git a/app-rust/src/android/transcript_client.rs b/app-rust/src/android/transcript_client.rs index dca8a3b..b8554c3 100644 --- a/app-rust/src/android/transcript_client.rs +++ b/app-rust/src/android/transcript_client.rs @@ -99,7 +99,7 @@ impl AndroidAppState for TranscriptClient { .span(Dir::DOWN) .add_strong(rsc) .any(); - ui_state.set_root(tree); + ui_state.set_root(rsc, tree); let mut client = Self { ui_state, diff --git a/app-rust/src/ui/fixture.rs b/app-rust/src/ui/fixture.rs index 08ed3fc..1daac68 100644 --- a/app-rust/src/ui/fixture.rs +++ b/app-rust/src/ui/fixture.rs @@ -99,12 +99,15 @@ where )) } -pub fn open(rsc: &mut Rsc, ui_state: &mut impl HasRoot) -> Result +pub fn open( + rsc: &mut Rsc, + ui_state: &mut impl HasRoot, +) -> Result where Rsc::State: FocusHost + OpenUrl, { let (opened, tree) = build_screen(rsc)?; - ui_state.set_root(tree); + ui_state.set_root(rsc, tree); Ok(opened) } diff --git a/app-rust/src/ui/mod.rs b/app-rust/src/ui/mod.rs index 2dd3dc0..aaf68eb 100644 --- a/app-rust/src/ui/mod.rs +++ b/app-rust/src/ui/mod.rs @@ -224,14 +224,14 @@ impl TranscriptScreen { pub fn build( rsc: &mut Rsc, - ui_state: &mut impl HasRoot, + ui_state: &mut impl HasRoot, rows: Vec, ) -> TranscriptScreen where Rsc::State: FocusHost + OpenUrl, { let (screen, tree) = build_tree(rsc, rows); - ui_state.set_root(tree); + ui_state.set_root(rsc, tree); screen } @@ -281,10 +281,9 @@ where { list.on(CursorSense::drag_senses(), move |ctx, rsc: &mut Rsc| { let input = &ctx.data; - rsc.with_nearest_controller::( - list, - |id, selection, rsc| selection.drag(id, rsc, input), - ); + rsc.with_nearest_controller::(list, |id, selection, rsc| { + selection.drag(id, rsc, input) + }); }) .add(rsc); } diff --git a/app-rust/src/ui/row.rs b/app-rust/src/ui/row.rs index 67a4ab6..67906a8 100644 --- a/app-rust/src/ui/row.rs +++ b/app-rust/src/ui/row.rs @@ -214,10 +214,9 @@ where let (pos, size) = (ctx.data.pos, ctx.data.size); let input = &ctx.data; let outcome = rsc - .with_nearest_controller::( - field, - |id, selection, rsc| selection.drag(id, rsc, input), - ) + .with_nearest_controller::(field, |id, selection, rsc| { + selection.drag(id, rsc, input) + }) .unwrap_or(SelectionInput::Tapped); // A *tap*, decided by the same `DragArbiter` the pan and // the selection are: a gesture that panned the list past diff --git a/app-rust/src/ui/tap.rs b/app-rust/src/ui/tap.rs index 3bd8b32..875c0fc 100644 --- a/app-rust/src/ui/tap.rs +++ b/app-rust/src/ui/tap.rs @@ -11,10 +11,9 @@ pub(crate) fn on_tap( ptr.on(CursorSense::drag_senses(), move |ctx, rsc: &mut Rsc| { let input = &ctx.data; let outcome = rsc - .with_nearest_controller::( - list, - |id, selection, rsc| selection.drag(id, rsc, input), - ) + .with_nearest_controller::(list, |id, selection, rsc| { + selection.drag(id, rsc, input) + }) .unwrap_or(SelectionInput::Tapped); if outcome == SelectionInput::Tapped { f(rsc); diff --git a/app-rust/tests/top_edge.rs b/app-rust/tests/top_edge.rs index e6f4a16..b90c534 100644 --- a/app-rust/tests/top_edge.rs +++ b/app-rust/tests/top_edge.rs @@ -14,7 +14,7 @@ fn opened() -> (Harness, ai_app::ui::TranscriptScreen) { .span(Dir::DOWN) .add_strong(&mut h.rsc) .any(); - h.state.set_root(root); + h.state.set_root(&mut h.rsc, root); h.frame(0); h.frame(PHONE_FRAME_MS); (h, opened.screen) diff --git a/docs/IRIS_TODO.md b/docs/IRIS_TODO.md index 877d495..1bd9271 100644 --- a/docs/IRIS_TODO.md +++ b/docs/IRIS_TODO.md @@ -30,11 +30,6 @@ Framework capabilities needed by `RUST.md`'s port plan: 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 - toggle switch. Build once, wherever it is first needed, rather than - once per screen that wants one. - [ ] **A horizontal gauge/bar widget.** (**P1**.) For `SessionUsageBar`'s equivalent — a bounded fill reflecting a fraction, nothing fancier. diff --git a/docs/RUST.md b/docs/RUST.md index cbe00e7..a258c22 100644 --- a/docs/RUST.md +++ b/docs/RUST.md @@ -678,8 +678,8 @@ it is both. This is an `app-rust` defect, not an iris framework item. `AI_SANDBOX_BIG_MB` and `--delay`. - [ ] **P1d — images, the session settings dialog, attachments, usage bar.** `SessionImage` thumbnails (the scaled image - widget), the modal primitive and `SessionSettingsDialog`/ - `UsageDialog`, `PendingAttachments` over the attachments + widget), the existing overlay facility and + `SessionSettingsDialog`/`UsageDialog`, `PendingAttachments` over the attachments route (`api.rs` gap), `SessionUsageBar` (the gauge widget). - [ ] **P1e — keyboard and insets behaviours** from AGENTS.md's "Things that have bitten", re-verified on the phone build: @@ -715,11 +715,11 @@ it is both. This is an `app-rust` defect, not an iris framework item. **iris widgets missing, → `IRIS_TODO.md`'s "Build (for the port)" section**: the distance-to-unloaded-edge query P1c needs for its viewport-sized history cushion; per-range text backgrounds for inline - code; a fitted image widget; one modal/dialog primitive reused by the - settings and usage dialogs; and a horizontal gauge for - `SessionUsageBar`. Tappable links already exist. Row accessibility - names are app content applied through iris's existing `.label()` API, - not a missing framework widget. + code; a fitted image widget; and a horizontal gauge for + `SessionUsageBar`. The settings and usage dialogs can use Iris's + existing local overlay hosts. Tappable links already exist. Row + accessibility names are app content applied through iris's existing + `.label()` API, not a missing framework widget. **Pass condition**: `app/ui-sandbox.sh`'s fixtures driven by `ui-trace record --do "tap '