1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Hand out an AccessKit focus request at the start of the pass

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.

A widget in `focus_widgets_cache` has asked for focus in an earlier pass, so it
is one we can hand focus to in `begin_pass`, 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.

`interested_in_focus` keeps the same grant as a fallback, for the first pass a
widget is focusable in, and the two share `take_focus_from_accesskit`.

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.
This commit is contained in:
Lucas Meurer
2026-08-25 16:11:28 +02:00
parent 887d9b0415
commit cf4b8de8bd
8 changed files with 46 additions and 26 deletions

View File

@@ -618,6 +618,27 @@ impl Focus {
self.id_requested_by_accesskit = Some(*target_node); self.id_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| {
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);
}
}
/// 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<Rect>) { pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
@@ -645,11 +666,10 @@ impl Focus {
} }
fn interested_in_focus(&mut self, id: Id) { 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()) { if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.focused_widget = Some(FocusWidget::new(id)); self.take_focus_from_accesskit(id);
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.reset_focus();
} }
// The rect is updated at the end of the frame. // The rect is updated at the end of the frame.

View File

@@ -603,7 +603,7 @@ impl Widget for DragValue<'_> {
.clip_text(false) .clip_text(false)
.horizontal_align(ui.layout().horizontal_align()) .horizontal_align(ui.layout().horizontal_align())
.vertical_align(ui.layout().vertical_align()) .vertical_align(ui.layout().vertical_align())
.min_size(min_size.unwrap_or(ui.spacing().interact_size)) .min_size(min_size.unwrap_or_else(|| ui.spacing().interact_size))
.id(id) .id(id)
.desired_width( .desired_width(
ui.spacing().interact_size.x - 2.0 * ui.spacing().button_padding.x, ui.spacing().interact_size.x - 2.0 * ui.spacing().button_padding.x,
@@ -654,7 +654,7 @@ impl Widget for DragValue<'_> {
.wrap_mode(TextWrapMode::Extend) .wrap_mode(TextWrapMode::Extend)
.sense(Sense::click_and_drag()) .sense(Sense::click_and_drag())
.gap(0.0) .gap(0.0)
.min_size(min_size.unwrap_or(ui.spacing().interact_size)); // TODO(emilk): find some more generic solution to `min_size` .min_size(min_size.unwrap_or_else(|| ui.spacing().interact_size)); // TODO(emilk): find some more generic solution to `min_size`
let cursor_icon = if value <= *range.start() { let cursor_icon = if value <= *range.start() {
CursorIcon::ResizeEast CursorIcon::ResizeEast
@@ -822,6 +822,16 @@ fn select_all_text(ui: &Ui, widget_id: Id, response_id: Id, value_text: &str) {
state.store(ui.ctx(), response_id); state.store(ui.ctx(), response_id);
} }
impl HasClasses for DragValue<'_> {
fn classes(&self) -> &Classes {
&self.classes
}
fn classes_mut(&mut self) -> &mut Classes {
&mut self.classes
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::clamp_value_to_range; use super::clamp_value_to_range;
@@ -881,13 +891,3 @@ mod tests {
); );
} }
} }
impl HasClasses for DragValue<'_> {
fn classes(&self) -> &Classes {
&self.classes
}
fn classes_mut(&mut self) -> &mut Classes {
&mut self.classes
}
}

View File

@@ -745,7 +745,7 @@ impl TextEdit<'_> {
let allocated = AtomLayout::new(atoms) let allocated = AtomLayout::new(atoms)
.id(id) .id(id)
.fallback_gap(gap) .gap(gap)
.min_size(Vec2::new(allocate_width, min_height.at_least(min_size.y))) .min_size(Vec2::new(allocate_width, min_height.at_least(min_size.y)))
.max_width(allocate_width) .max_width(allocate_width)
.sense(sense) .sense(sense)

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:570216d6ffa3cc278705582d95b96200bc3ef1608b8f2983a6ed3f8b2ee7276c oid sha256:806f3f69228e62d9d0838fb27a02f46cae29efffee54d8182ecbfd2c12f405ba
size 2280 size 2413

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b66afca19d0a0a6fac4737816443fbd481635897807689dbd75321360ffa079d oid sha256:2ae71de200dfb36f64e1ce95c2ad78ef3c1e88e792ada5b5fa1d8fa8c938d8fd
size 13803 size 13806

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:20afbde3e1edc7a599936f83188fb1a2c35d2d1344ce7ad8e115b937266481fc oid sha256:10459c5ac931ee55d814e7e4122167f1d4050fff39c409cef995c91d6deaef55
size 2547 size 2563

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4b0af10fca47b33752aed2549528dba6b6e8c4818f2fc2b9de60451afdd30a2b oid sha256:123f7d1a86d644d2e830a2c8b73e8c509ce33fd5d3ead28d3eccf6180a2fcfc5
size 1690 size 1790

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:76be7b08da637ecbf898efd8322615d5e2f1cb1757665e5486db86ccb7fb3438 oid sha256:f916e217bd4beb3637acb5394a7da876f9ec684860c9ca922f8bfa3ded6a2684
size 9462 size 9462