1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 14:49:06 -04:00

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
This commit is contained in:
Lucas Meurer
2026-01-05 12:50:52 +01:00
committed by GitHub
parent 06e491c5ec
commit f9bf0ee6c4
6 changed files with 37 additions and 12 deletions

View File

@@ -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()
}

View File

@@ -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()
};

View File

@@ -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
{

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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"));
}
}