From 5432f91a4d764127548093017d8b6537c8321588 Mon Sep 17 00:00:00 2001 From: Fleabit <65809749+fleabitdev@users.noreply.github.com> Date: Sun, 7 Jan 2024 16:04:29 +0000 Subject: [PATCH] Have non-resizable Areas ignore mouse input just beyond their bounds (#3039) As described in #576, the method `Memory::layer_id_at` expands the hit-testing region for every `Area` slightly. This is necessary so that, when the user clicks to resize a `Window`, the mouse input isn't routed to a different `Window`. For non-resizable `Area`s (such as dropdown menus, context menus, date pickers, and any non-resizable `Window`), this causes them to be surrounded by a "dead zone" where any underlying widgets can't be hovered or clicked. The effect is particularly noticeable in menu bars. This commit adds a persisted `edges_padded_for_resize` property to `Area`, which is `true` when the `Area` belongs to a resizable `Window` and `false` for all other `Area`s. The hit-testing region is only expanded when this property is `true`. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/area.rs | 17 +++++++++++++++++ crates/egui/src/containers/window.rs | 10 +++++++++- crates/egui/src/context.rs | 1 + crates/egui/src/memory.rs | 7 +++++-- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 52abb7499ed..e2f73e1cd57 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -20,6 +20,10 @@ pub(crate) struct State { /// If false, clicks goes straight through to what is behind us. /// Good for tooltips etc. pub interactable: bool, + + /// When `true`, this `Area` belongs to a resizable window, so it needs to + /// receive mouse input which occurs a short distance beyond its bounding rect. + pub edges_padded_for_resize: bool, } impl State { @@ -71,6 +75,7 @@ pub struct Area { pivot: Align2, anchor: Option<(Align2, Vec2)>, new_pos: Option, + edges_padded_for_resize: bool, } impl Area { @@ -87,6 +92,7 @@ impl Area { new_pos: None, pivot: Align2::LEFT_TOP, anchor: None, + edges_padded_for_resize: false, } } @@ -217,6 +223,14 @@ impl Area { Align2::LEFT_TOP } } + + /// When `true`, this `Area` belongs to a resizable window, so it needs to + /// receive mouse input which occurs a short distance beyond its bounding rect. + #[inline] + pub(crate) fn edges_padded_for_resize(mut self, edges_padded_for_resize: bool) -> Self { + self.edges_padded_for_resize = edges_padded_for_resize; + self + } } pub(crate) struct Prepared { @@ -261,6 +275,7 @@ impl Area { anchor, constrain, constrain_rect, + edges_padded_for_resize, } = self; let layer_id = LayerId::new(order, id); @@ -281,9 +296,11 @@ impl Area { pivot, size: Vec2::ZERO, interactable, + edges_padded_for_resize, }); state.pivot_pos = new_pos.unwrap_or(state.pivot_pos); state.interactable = interactable; + state.edges_padded_for_resize = edges_padded_for_resize; if let Some((anchor, offset)) = anchor { let screen = ctx.available_rect(); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 6383125bb14..f1b5f130844 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -48,7 +48,9 @@ impl<'open> Window<'open> { /// If you need a changing title, you must call `window.id(…)` with a fixed id. pub fn new(title: impl Into) -> Self { let title = title.into().fallback_text_style(TextStyle::Heading); - let area = Area::new(Id::new(title.text())).constrain(true); + let area = Area::new(Id::new(title.text())) + .constrain(true) + .edges_padded_for_resize(true); Self { title, open: None, @@ -117,6 +119,9 @@ impl<'open> Window<'open> { #[inline] pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self { self.resize = mutate(self.resize); + self.area = self + .area + .edges_padded_for_resize(self.resize.is_resizable()); self } @@ -273,6 +278,7 @@ impl<'open> Window<'open> { #[inline] pub fn fixed_size(mut self, size: impl Into) -> Self { self.resize = self.resize.fixed_size(size); + self.area = self.area.edges_padded_for_resize(false); self } @@ -294,6 +300,7 @@ impl<'open> Window<'open> { #[inline] pub fn resizable(mut self, resizable: bool) -> Self { self.resize = self.resize.resizable(resizable); + self.area = self.area.edges_padded_for_resize(resizable); self } @@ -319,6 +326,7 @@ impl<'open> Window<'open> { pub fn auto_sized(mut self) -> Self { self.resize = self.resize.auto_sized(); self.scroll = ScrollArea::neither(); + self.area = self.area.edges_padded_for_resize(false); self } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index b242144f130..27546a18056 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -317,6 +317,7 @@ impl ContextImpl { pivot: Align2::LEFT_TOP, size: screen_rect.size(), interactable: true, + edges_padded_for_resize: false, }, ); diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 86e2164d5b7..3b122a6d49c 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -862,8 +862,11 @@ impl Areas { if let Some(state) = self.areas.get(&layer.id) { let mut rect = state.rect(); if state.interactable { - // Allow us to resize by dragging just outside the window: - rect = rect.expand(resize_interact_radius_side); + if state.edges_padded_for_resize { + // Allow us to resize by dragging just outside the window: + rect = rect.expand(resize_interact_radius_side); + } + if rect.contains(pos) { return Some(*layer); }