From 0afbefc884cc6d8ada9c60461b1b7c89415270b1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Mar 2024 12:32:54 +0100 Subject: [PATCH] Improve logic for when submenus are kept open (#4166) * Closes https://github.com/emilk/egui/issues/2853 * Closes https://github.com/emilk/egui/issues/4101 * Reverts parts of https://github.com/emilk/egui/pull/3055 --- crates/egui/src/menu.rs | 34 +++++++++++++++++++++++++--------- crates/egui/src/ui.rs | 7 ++++++- crates/emath/src/rect.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 67689ca9..1f7a74fb 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -613,30 +613,42 @@ impl MenuState { let pointer = ui.input(|i| i.pointer.clone()); let open = self.is_open(sub_id); if self.moving_towards_current_submenu(&pointer) { + // We don't close the submenu if the pointer is on its way to hover it. // ensure to repaint once even when pointer is not moving ui.ctx().request_repaint(); } else if !open && button.hovered() { let pos = button.rect.right_top(); self.open_submenu(sub_id, pos); + } else if open + && ui.interact_bg(Sense::hover()).contains_pointer() + && !button.hovered() + && !self.hovering_current_submenu(&pointer) + { + // We are hovering something else in the menu, so close the submenu. + self.close_submenu(); } } - /// Check if `dir` points from `pos` towards left side of `rect`. - fn points_at_left_of_rect(pos: Pos2, dir: Vec2, rect: Rect) -> bool { - let vel_a = dir.angle(); - let top_a = (rect.left_top() - pos).angle(); - let bottom_a = (rect.left_bottom() - pos).angle(); - bottom_a - vel_a >= 0.0 && top_a - vel_a <= 0.0 - } - /// Check if pointer is moving towards current submenu. fn moving_towards_current_submenu(&self, pointer: &PointerState) -> bool { if pointer.is_still() { return false; } + if let Some(sub_menu) = self.current_submenu() { if let Some(pos) = pointer.hover_pos() { - return Self::points_at_left_of_rect(pos, pointer.velocity(), sub_menu.read().rect); + let rect = sub_menu.read().rect; + return rect.intesects_ray(pos, pointer.velocity().normalized()); + } + } + false + } + + /// Check if pointer is hovering current submenu. + fn hovering_current_submenu(&self, pointer: &PointerState) -> bool { + if let Some(sub_menu) = self.current_submenu() { + if let Some(pos) = pointer.hover_pos() { + return sub_menu.read().area_contains(pos); } } false @@ -673,4 +685,8 @@ impl MenuState { self.sub_menu = Some((id, Arc::new(RwLock::new(Self::new(pos))))); } } + + fn close_submenu(&mut self) { + self.sub_menu = None; + } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 4e28ece4..56db9e97 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -714,8 +714,13 @@ impl Ui { .rect_contains_pointer(self.layer_id(), self.clip_rect().intersect(rect)) } - /// Is the pointer (mouse/touch) above this [`Ui`]? + /// Is the pointer (mouse/touch) above the current [`Ui`]? + /// /// Equivalent to `ui.rect_contains_pointer(ui.min_rect())` + /// + /// Note that this tests against the _current_ [`Ui::min_rect`]. + /// If you want to test against the final `min_rect`, + /// use [`Self::interact_bg`] instead. pub fn ui_contains_pointer(&self) -> bool { self.rect_contains_pointer(self.min_rect()) } diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 4e6ee362..812d76c0 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -605,6 +605,32 @@ impl Rect { } } +impl Rect { + /// Does this Rect intersect the given ray (where `d` is normalized)? + pub fn intesects_ray(&self, o: Pos2, d: Vec2) -> bool { + let mut tmin = -f32::INFINITY; + let mut tmax = f32::INFINITY; + + if d.x != 0.0 { + let tx1 = (self.min.x - o.x) / d.x; + let tx2 = (self.max.x - o.x) / d.x; + + tmin = tmin.max(tx1.min(tx2)); + tmax = tmax.min(tx1.max(tx2)); + } + + if d.y != 0.0 { + let ty1 = (self.min.y - o.y) / d.y; + let ty2 = (self.max.y - o.y) / d.y; + + tmin = tmin.max(ty1.min(ty2)); + tmax = tmax.min(ty1.max(ty2)); + } + + tmin <= tmax + } +} + impl std::fmt::Debug for Rect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{:?} - {:?}]", self.min, self.max)