From f9bf0ee6c43c3136cbde0084e8a09bf09e89fdac Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 5 Jan 2026 12:50:52 +0100 Subject: [PATCH] Don't focus Areas, Windows and ScrollAreas (#7827) Currently, tabbing through egui demo app, there are a lot of widgets that have invisible focus. Tabbing into a window for example takes 10 (!) presses of the tab key before the first widget within the window is focused. Before that, the focus moves to each resize handle, the scroll area and the scroll bar. At that point a user might think the focus is entirely broken. This pr removes the focusable sense from all these elements. Anything that can be focused should somehow indicate that it currently has focus, or the user could get frustrated. It also adds a debug flag to always show the focused widget, so it's easier to debug these cases --- crates/egui/src/containers/area.rs | 4 ++-- crates/egui/src/containers/scroll_area.rs | 4 ++-- crates/egui/src/containers/window.rs | 8 ++++---- crates/egui/src/context.rs | 9 ++++++++- crates/egui/src/sense.rs | 10 +++++++--- crates/egui/src/style.rs | 14 ++++++++++++++ 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 3b6d4006e..4333cf73a 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -505,9 +505,9 @@ impl Area { let interact_id = layer_id.id.with("move"); let sense = sense.unwrap_or_else(|| { if movable { - Sense::drag() + Sense::DRAG } else if interactable { - Sense::click() // allow clicks to bring to front + Sense::CLICK // allow clicks to bring to front } else { Sense::hover() } diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index afe1239c7..8f46223d2 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -810,7 +810,7 @@ impl ScrollArea { // or we will steal input from the widgets we contain. let content_response_option = state .interact_rect - .map(|rect| ui.interact(rect, id.with("area"), Sense::drag())); + .map(|rect| ui.interact(rect, id.with("area"), Sense::DRAG)); if content_response_option .as_ref() @@ -1276,7 +1276,7 @@ impl Prepared { }; let sense = if scroll_source.scroll_bar && ui.is_enabled() { - Sense::click_and_drag() + Sense::CLICK | Sense::DRAG } else { Sense::hover() }; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 6dc7927ae..c6b739589 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -937,7 +937,7 @@ fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction fn do_resize_interaction( ctx: &Context, possible: PossibleInteractions, - _accessibility_parent: Id, + accessibility_parent: Id, layer_id: LayerId, outer_rect: Rect, window_frame: Frame, @@ -957,14 +957,14 @@ fn do_resize_interaction( let rect = outer_rect.shrink(window_frame.stroke.width / 2.0); let side_response = |rect, id| { - ctx.register_accesskit_parent(id, _accessibility_parent); + ctx.register_accesskit_parent(id, accessibility_parent); let response = ctx.create_widget( WidgetRect { layer_id, id, rect, interact_rect: rect, - sense: Sense::drag(), + sense: Sense::DRAG, // Don't use Sense::drag() since we don't want these to be focusable enabled: true, }, true, @@ -1324,7 +1324,7 @@ impl TitleBar { let id = ui.unique_id().with("__window_title_bar"); if ui - .interact(double_click_rect, id, Sense::click()) + .interact(double_click_rect, id, Sense::CLICK) .double_clicked() && collapsible { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index f8996b8a3..e28048edf 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2432,7 +2432,8 @@ impl Context { if let Some(widget) = self.write(|ctx| ctx.viewport().this_pass.widgets.get(id).copied()) { - paint_widget(&widget, text, color); + let text = format!("{text} - {id:?}"); + paint_widget(&widget, &text, color); } }; @@ -2541,6 +2542,12 @@ impl Context { } } + if self.global_style().debug.show_focused_widget + && let Some(focused_id) = self.memory(|mem| mem.focused()) + { + paint_widget_id(focused_id, "focused", Color32::PURPLE); + } + if let Some(debug_rect) = self.pass_state_mut(|fs| fs.debug_rect.take()) { debug_rect.paint(&self.debug_painter()); } diff --git a/crates/egui/src/sense.rs b/crates/egui/src/sense.rs index 464db311f..c3b3af7f2 100644 --- a/crates/egui/src/sense.rs +++ b/crates/egui/src/sense.rs @@ -53,19 +53,23 @@ impl Sense { Self::FOCUSABLE } - /// Sense clicks and hover, but not drags. + /// Sense clicks and hover, but not drags, and make the widget focusable. + /// + /// Use [`Sense::CLICK`] if you don't want the widget to be focusable. #[inline] pub fn click() -> Self { Self::CLICK | Self::FOCUSABLE } - /// Sense drags and hover, but not clicks. + /// Sense drags and hover, but not clicks. Make the widget focusable. + /// + /// Use [`Sense::DRAG`] if you don't want the widget to be focusable #[inline] pub fn drag() -> Self { Self::DRAG | Self::FOCUSABLE } - /// Sense both clicks, drags and hover (e.g. a slider or window). + /// Sense both clicks, drags and hover (e.g. a slider or window), and make the widget focusable. /// /// Note that this will introduce a latency when dragging, /// because when the user starts a press egui can't know if this is the start diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index f94b9d345..a555b9ace 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1303,6 +1303,13 @@ pub struct DebugOptions { /// /// See [`emath::GuiRounding`] for more. pub show_unaligned: bool, + + /// Highlight the currently focused widget. + /// + /// This is useful when some widget has a invisible focus (e.g. when a widget is using + /// `Sense::click()` when it should be using `Sense::CLICK`) and you need to find which one it + /// is. + pub show_focused_widget: bool, } #[cfg(debug_assertions)] @@ -1319,6 +1326,7 @@ impl Default for DebugOptions { show_interactive_widgets: false, show_widget_hits: false, show_unaligned: cfg!(debug_assertions), + show_focused_widget: false, } } } @@ -2480,6 +2488,7 @@ impl DebugOptions { show_interactive_widgets, show_widget_hits, show_unaligned, + show_focused_widget, } = self; { @@ -2514,6 +2523,11 @@ impl DebugOptions { "Show rectangles not aligned to integer point coordinates", ); + ui.checkbox( + show_focused_widget, + "Highlight which widget has keyboard focus", + ); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options")); } }