iris/android: composing text sync, tap-vs-swipe focus, composer rebuild, atlas reset on app-switch

Four fixes from Iris's phone report on the dc01f88 build, plus her same-day
follow-up on swipe-vs-tap:

- android/ime.rs: InputConnection now calls InputMethodManager.updateSelection
  after every edit (new update_ime_selection, called from after_input) -- Gboard
  was holding keystrokes back with nothing telling it the app's selection/
  composing region had moved, which read as "doesn't enter it until I hit
  space, doesn't move the caret". New unit tests in widget/text/edit.rs cover
  the buffer-level composing/commit/delete/selection operations directly.

- attr.rs: Selector/Selectable rewritten around a shared on_press dispatcher
  over PressStart/Pressing/PressEnd instead of click_or_drag(), so a field
  that isn't already focused only grants focus (and requests the IME) on a
  completed tap -- press and release with no frame past DRAG_SLOP. A drag
  is never consumed, so whatever is behind the field still sees it. New
  FocusHost::is_focused (both platform impls) and TextEdit::press_origin
  back this. Verified on the emulator: dumpsys input_method's mInputShown
  stays false after a swipe over the composer, true after a tap.

- iris_core: GlyphAtlas::clear()/Textures::reset(), called together from
  android/view.rs's surface_changed exactly when a genuinely new renderer is
  built (app-switch, not the keyboard-resize path that already reuses the
  renderer) -- both CPU-side caches otherwise kept pointing at the old,
  destroyed device's textures. Verified on the emulator: home, reopen, every
  glyph still on screen.

- transcript-ui/composer.rs: rebuilt as one widget (unchanged Stack{rect,
  span} idiom, capped at ~6 lines via MaxSize + .scrollable(), wrapped in one
  Pad whose bottom Composer::set_bottom_inset rewrites in place so the bar
  sits on the IME or nav-bar inset with no rebuild -- rebuilding would drop
  focus/selection/in-progress text). Wired from bench_client.rs's existing
  on_insets_changed.

A second, deeper bug found while verifying the composing fix is NOT fixed
this pass: composed text never becomes visible at all. A new layout_tests.rs
test proves the widget tree's own region math is correct across a keyboard
resize, ruling that out; RUST.md's P0 box has the full writeup and what to
check next (UiRenderState::redraw's single-widget path, or something
force-gles-specific -- this AVD has no Vulkan adapter to rule that out with).

cargo fmt/clippy/test --workspace and cargo ndk clippy all clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-06 02:03:00 -04:00
1 parent dc01f88d75
commit 20b12255e1
14 files changed
+628 -15

No files matched your search

+21
View File
@@ -141,6 +141,27 @@ impl Textures {
self.updates.push(Update::Patch(handle.slot, rect));
}
/// Forget every image, page and pending update -- what a genuinely new
/// GPU device needs alongside [`crate::render::atlas::GlyphAtlas::
/// clear`], which this module's own doc references: every slot number
/// and every queued [`Update`] here describes the *old* device's
/// textures (an `Update::Push`/`Update::Patch` already drained into a
/// renderer that no longer exists is gone for good, and a fresh
/// `UiRenderNode`'s own texture manager starts with none of them
/// applied), so nothing is lost by starting this bookkeeping over too.
/// Any `TextureHandle` a caller still holds across the reset (none in
/// the transcript screen this reset is wired up for today -- confirmed
/// by grep, the only standalone (non-atlas) image anywhere in this
/// workspace is `iris/widget/image.rs`'s `Image`, used by the separate
/// `tabs-ui` example) is left pointing at a slot this instance no
/// longer recognises and needs reinserting via `add`/`add_page` again
/// -- the same pre-existing gap a renderer restart already left for
/// such a handle before this method existed, just named rather than
/// silent now.
pub fn reset(&mut self) {
*self = Self::new();
}
pub fn free(&mut self) {
for (kind, idx) in self.recv.try_iter() {
self.images[idx as usize] = None;
+19
View File
@@ -173,6 +173,25 @@ impl GlyphAtlas {
pub fn glyph_count(&self) -> usize {
self.entries.len()
}
/// Forget every page and every rasterised entry -- what a genuinely new
/// GPU device needs (`android::view::IrisViewPeer::surface_changed`'s
/// "not already live" branch, e.g. after backgrounding): the pages this
/// atlas remembers are `TextureHandle`s into the *old* device's
/// textures, which no longer exist, and every `GlyphEntry`'s `uv_min`/
/// `uv_max`/`layer` point into them. Without this, a glyph already
/// cached here is treated as "already placed" and never re-inserted
/// into the fresh (empty) atlas the new renderer actually has --
/// exactly the "rectangles stay, glyphs disappear" bug the resize path
/// (`AndroidRenderer::resize`) was built to avoid for the reuse case;
/// this is its counterpart for the case where the renderer really is
/// new. Dropping `pages` also drops its `TextureHandle`s, which send a
/// free message back through their `Textures`; see `Textures::reset`'s
/// doc for why that is harmless here.
pub fn clear(&mut self) {
self.pages.clear();
self.entries.clear();
}
}
fn fits(page: &Page, need_w: u32, need_h: u32) -> bool {