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
This commit is contained in:
Emil Ernerfeldt 2024-03-13 12:32:54 +01:00 committed by GitHub
parent f019032033
commit 0afbefc884
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 57 additions and 10 deletions

View File

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

View File

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

View File

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