mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 12:50:04 -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:
@@ -618,6 +618,27 @@ impl Focus {
|
||||
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>) {
|
||||
@@ -645,11 +666,10 @@ 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.focused_widget = Some(FocusWidget::new(id));
|
||||
self.id_requested_by_accesskit = None;
|
||||
self.give_to_next = false;
|
||||
self.reset_focus();
|
||||
self.take_focus_from_accesskit(id);
|
||||
}
|
||||
|
||||
// The rect is updated at the end of the frame.
|
||||
|
||||
@@ -603,7 +603,7 @@ impl Widget for DragValue<'_> {
|
||||
.clip_text(false)
|
||||
.horizontal_align(ui.layout().horizontal_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)
|
||||
.desired_width(
|
||||
ui.spacing().interact_size.x - 2.0 * ui.spacing().button_padding.x,
|
||||
@@ -654,7 +654,7 @@ impl Widget for DragValue<'_> {
|
||||
.wrap_mode(TextWrapMode::Extend)
|
||||
.sense(Sense::click_and_drag())
|
||||
.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() {
|
||||
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);
|
||||
}
|
||||
|
||||
impl HasClasses for DragValue<'_> {
|
||||
fn classes(&self) -> &Classes {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
fn classes_mut(&mut self) -> &mut Classes {
|
||||
&mut self.classes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,7 +745,7 @@ impl TextEdit<'_> {
|
||||
|
||||
let allocated = AtomLayout::new(atoms)
|
||||
.id(id)
|
||||
.fallback_gap(gap)
|
||||
.gap(gap)
|
||||
.min_size(Vec2::new(allocate_width, min_height.at_least(min_size.y)))
|
||||
.max_width(allocate_width)
|
||||
.sense(sense)
|
||||
|
||||
Reference in New Issue
Block a user