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:
parent
f019032033
commit
0afbefc884
|
|
@ -613,30 +613,42 @@ impl MenuState {
|
||||||
let pointer = ui.input(|i| i.pointer.clone());
|
let pointer = ui.input(|i| i.pointer.clone());
|
||||||
let open = self.is_open(sub_id);
|
let open = self.is_open(sub_id);
|
||||||
if self.moving_towards_current_submenu(&pointer) {
|
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
|
// ensure to repaint once even when pointer is not moving
|
||||||
ui.ctx().request_repaint();
|
ui.ctx().request_repaint();
|
||||||
} else if !open && button.hovered() {
|
} else if !open && button.hovered() {
|
||||||
let pos = button.rect.right_top();
|
let pos = button.rect.right_top();
|
||||||
self.open_submenu(sub_id, pos);
|
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.
|
/// Check if pointer is moving towards current submenu.
|
||||||
fn moving_towards_current_submenu(&self, pointer: &PointerState) -> bool {
|
fn moving_towards_current_submenu(&self, pointer: &PointerState) -> bool {
|
||||||
if pointer.is_still() {
|
if pointer.is_still() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sub_menu) = self.current_submenu() {
|
if let Some(sub_menu) = self.current_submenu() {
|
||||||
if let Some(pos) = pointer.hover_pos() {
|
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
|
false
|
||||||
|
|
@ -673,4 +685,8 @@ impl MenuState {
|
||||||
self.sub_menu = Some((id, Arc::new(RwLock::new(Self::new(pos)))));
|
self.sub_menu = Some((id, Arc::new(RwLock::new(Self::new(pos)))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_submenu(&mut self) {
|
||||||
|
self.sub_menu = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -714,8 +714,13 @@ impl Ui {
|
||||||
.rect_contains_pointer(self.layer_id(), self.clip_rect().intersect(rect))
|
.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())`
|
/// 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 {
|
pub fn ui_contains_pointer(&self) -> bool {
|
||||||
self.rect_contains_pointer(self.min_rect())
|
self.rect_contains_pointer(self.min_rect())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
impl std::fmt::Debug for Rect {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "[{:?} - {:?}]", self.min, self.max)
|
write!(f, "[{:?} - {:?}]", self.min, self.max)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue