From 79d8827d7b443d2908d1f77b3bc5f16260cc8e4d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Jul 2024 21:33:22 +0200 Subject: [PATCH] Fix menus and interactive popups not closing when framerate was low (#4757) --- crates/egui/src/input_state.rs | 22 +++++++++++++++++++++- crates/egui/src/menu.rs | 2 +- crates/egui/src/response.rs | 6 +++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index e2fd693b7868..abd6028b9c7b 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -726,6 +726,9 @@ pub struct PointerState { /// Current velocity of pointer. velocity: Vec2, + /// Current direction of pointer. + direction: Vec2, + /// Recent movement of the pointer. /// Used for calculating velocity of pointer. pos_history: History, @@ -774,7 +777,8 @@ impl Default for PointerState { delta: Vec2::ZERO, motion: None, velocity: Vec2::ZERO, - pos_history: History::new(0..1000, 0.1), + direction: Vec2::ZERO, + pos_history: History::new(2..1000, 0.1), down: Default::default(), press_origin: None, press_start_time: None, @@ -889,6 +893,7 @@ impl PointerState { // When dragging a slider and the mouse leaves the viewport, we still want the drag to work, // so we don't treat this as a `PointerEvent::Released`. // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame. + self.pos_history.clear(); } Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta, _ => {} @@ -920,6 +925,8 @@ impl PointerState { self.last_move_time = time; } + self.direction = self.pos_history.velocity().unwrap_or_default().normalized(); + self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging; self @@ -944,11 +951,22 @@ impl PointerState { } /// Current velocity of pointer. + /// + /// This is smoothed over a few frames, + /// but can be ZERO when frame-rate is bad. #[inline(always)] pub fn velocity(&self) -> Vec2 { self.velocity } + /// Current direction of the pointer. + /// + /// This is less sensitive to bad framerate than [`Self::velocity`]. + #[inline(always)] + pub fn direction(&self) -> Vec2 { + self.direction + } + /// Where did the current click/drag originate? /// `None` if no mouse button is down. #[inline(always)] @@ -1284,6 +1302,7 @@ impl PointerState { delta, motion, velocity, + direction, pos_history: _, down, press_origin, @@ -1304,6 +1323,7 @@ impl PointerState { "velocity: [{:3.0} {:3.0}] points/sec", velocity.x, velocity.y )); + ui.label(format!("direction: {direction:?}")); ui.label(format!("down: {down:#?}")); ui.label(format!("press_origin: {press_origin:?}")); ui.label(format!("press_start_time: {press_start_time:?} s")); diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 4980f6f98239..9f795edcb0e8 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -683,7 +683,7 @@ impl MenuState { if let Some(sub_menu) = self.current_submenu() { if let Some(pos) = pointer.hover_pos() { let rect = sub_menu.read().rect; - return rect.intersects_ray(pos, pointer.velocity().normalized()); + return rect.intersects_ray(pos, pointer.direction().normalized()); } } false diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index aee287176f26..8043843019f2 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -594,9 +594,9 @@ impl Response { let is_tooltip_open = self.is_tooltip_open(); if is_tooltip_open { - let (pointer_pos, pointer_vel) = self + let (pointer_pos, pointer_dir) = self .ctx - .input(|i| (i.pointer.hover_pos(), i.pointer.velocity())); + .input(|i| (i.pointer.hover_pos(), i.pointer.direction())); if let Some(pointer_pos) = pointer_pos { if self.rect.contains(pointer_pos) { @@ -624,7 +624,7 @@ impl Response { if let Some(pos) = pointer_pos { let pointer_in_area_or_on_the_way_there = rect.contains(pos) - || rect.intersects_ray(pos, pointer_vel.normalized()); + || rect.intersects_ray(pos, pointer_dir.normalized()); if pointer_in_area_or_on_the_way_there { return true;