From 80ff62e2becefff03b4d526da220ea7251a506f5 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 25 Aug 2026 16:11:28 +0200 Subject: [PATCH] Hand out an AccessKit focus request at the start of the pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AccessKit asks for focus by node id, and `Focus::interested_in_focus` only picks the request up once the widget itself runs. Everything that reads the focus before the widget — styling it, for one — therefore saw the old focus, so a text edit was styled unfocused in the very pass it got focused, and lost its focus ring for a frame. AccessKit names the widget by the node id it read from the last tree we sent, so the widget has been here before and is in `focus_widgets_cache`: `begin_pass` can hand out the focus right away, the same way a `request_focus` from the last pass is handed out. Memory then holds the focus before any widget runs, and `Ui::widget_style` needs to know nothing about focus at all. For the same reason the request never has to outlive the pass it arrives in — a widget that has not run yet cannot have been named by it — so the pending `id_requested_by_accesskit` field becomes a local. The four `text_edit_*` snapshots gain a caret along with the ring, because the field is now focused for real in that pass rather than a pass later. Also drops `fallback_gap` from `TextEdit`, which has no `gap` of its own to defer to, and moves the `DragValue` `HasClasses` impl above its test module. --- crates/egui/src/memory/mod.rs | 37 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index d88556264..72bc94ae5 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -516,8 +516,6 @@ pub(crate) struct Focus { /// The ID of a widget to give the focus to in the next frame. id_next_frame: Option, - id_requested_by_accesskit: Option, - /// If set, the next widget that is interested in focus will automatically get it. /// Probably because the user pressed Tab. give_to_next: bool, @@ -575,10 +573,10 @@ impl Focus { } let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default(); - self.id_requested_by_accesskit = None; - self.focus_direction = FocusDirection::None; + let mut focus_requested_by_accesskit = None; + for event in &new_input.events { if !event_filter.matches(event) && let crate::Event::Key { @@ -615,32 +613,27 @@ impl Focus { }) = event && *target_tree == accesskit::TreeId::ROOT { - self.id_requested_by_accesskit = Some(*target_node); + focus_requested_by_accesskit = Some(*target_node); } } - // `interested_in_focus` only picks the request up once the widget runs, which is too late - // for anything that reads the focus before it — styling the widget, for one. A widget in - // the cache asked for focus in an earlier pass, so it is one we can hand focus to now. - let accesskit_focus = self.id_requested_by_accesskit.and_then(|node_id| { + // AccessKit names the widget by the node id it read from the last tree we sent, so the + // widget has been here before and is in the cache. Handing the focus out here, instead of + // waiting for the widget to ask for it, means everything that reads the focus before the + // widget runs — styling it, for one — already sees the new focus. + let newly_focused = focus_requested_by_accesskit.and_then(|node_id| { self.focus_widgets_cache .keys() .find(|id| id.accesskit_id() == node_id) .copied() }); - if let Some(id) = accesskit_focus { - self.take_focus_from_accesskit(id); + if let Some(id) = newly_focused { + self.focused_widget = Some(FocusWidget::new(id)); + self.give_to_next = false; + self.reset_focus(); } } - /// Give a widget the focus that AccessKit asked for. - fn take_focus_from_accesskit(&mut self, id: Id) { - self.focused_widget = Some(FocusWidget::new(id)); - self.id_requested_by_accesskit = None; - self.give_to_next = false; - self.reset_focus(); - } - pub(crate) fn end_pass(&mut self, used_ids: &IdMap) { if self.focus_direction.is_cardinal() && let Some(found_widget) = self.find_widget_in_direction(used_ids) @@ -666,12 +659,6 @@ impl Focus { } fn interested_in_focus(&mut self, id: Id) { - // `begin_pass` hands out the request as soon as it can, but it can only recognize a widget - // that has been here before. This catches the first pass a widget is focusable in. - if self.id_requested_by_accesskit == Some(id.accesskit_id()) { - self.take_focus_from_accesskit(id); - } - // The rect is updated at the end of the frame. self.focus_widgets_cache .entry(id)