From ae8380b1ac7f6e0cca55633a8a49a6491110cc26 Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Tue, 2 Jun 2026 14:10:37 +0200 Subject: [PATCH] event_loop: retry focus requests that land before the view is rebuilt `take_focus_request()` looks up the target widget by `WidgetId` in `widget_rects`. Read-only `TextEdit` widgets are not tracked as interactive and therefore absent from `widget_rects`. A swipe-reveal flips the fields from read-only to interactive on `SlideRevealed`, but an intervening input event (e.g. the touch-up at the end of the swipe) can cause `dispatch()` to return before the vblank frame callback clears `frame_pending`, skipping the draw that would have rebuilt the widget tree. When the focus request fires in that same iteration it finds nothing and is consumed without effect. `AppData` gains a `focus_retry` field. When `take_focus_request()` (or a previous retry) returns an id but the widget is not in `widget_rects`, the id is stored in `focus_retry`, `view_dirty` is set and a redraw is requested. The next time around the view has been rebuilt with interactive fields and the widget is present. --- src/event_loop/app_data.rs | 3 +++ src/event_loop/run.rs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/event_loop/app_data.rs b/src/event_loop/app_data.rs index 7190450..68d8296 100644 --- a/src/event_loop/app_data.rs +++ b/src/event_loop/app_data.rs @@ -248,6 +248,9 @@ pub struct AppData /// Set after the first commit of a rendered buffer on the main surface. /// Used to drive `App::on_first_frame_committed` exactly once. pub first_frame_committed: bool, + /// Focus request deferred because the target widget was absent from + /// `widget_rects` (e.g. read_only, not yet rebuilt). Retried next draw. + pub focus_retry: Option, } impl AppData diff --git a/src/event_loop/run.rs b/src/event_loop/run.rs index c46e3a2..0a6f77f 100644 --- a/src/event_loop/run.rs +++ b/src/event_loop/run.rs @@ -329,6 +329,7 @@ pub( crate ) fn try_run( app: A ) -> Result<(), RunError> view_dirty: true, overlays_dirty: true, first_frame_committed: false, + focus_retry: None, }; // Register a calloop channel so the app can send messages from any thread. @@ -809,7 +810,8 @@ pub( crate ) fn try_run( app: A ) -> Result<(), RunError> // Walks main + every overlay because the targeted widget can live // on any of them (a search field on a launcher overlay, a text // edit on a dialog modal, …). - if let Some( id ) = data.app.take_focus_request() + let focus_id = data.app.take_focus_request().or_else( || data.focus_retry.take() ); + if let Some( id ) = focus_id { let mut hit: Option<( SurfaceFocus, usize )> = data.main.widget_rects.iter() .find( |w| w.id == Some( id ) ) @@ -840,6 +842,10 @@ pub( crate ) fn try_run( app: A ) -> Result<(), RunError> } } } + } else { + data.focus_retry = Some( id ); + data.view_dirty = true; + data.main.request_redraw(); } } }