From b8048572e8cc47ef9410b3516456da2a320fcdd2 Mon Sep 17 00:00:00 2001 From: Giantblargg Date: Tue, 20 Feb 2024 10:00:39 -0700 Subject: [PATCH] Add API for raw mouse motion (#4063) Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen. It is useful in certain situations such as draggable values and 3D cameras, where screen position does not matter. https://github.com/emilk/egui/assets/1700581/1400e6a6-0573-41b9-99a1-a9cd305aa1a3 Added `Event::MouseMoved` for integrations to supply raw mouse movement. Added `Response:drag_motion` to get the raw mouse movement, but will fall back to delta in case the integration does not supply it. Nothing should be breaking, but third-party integrations that can send `Event::MouseMoved` should be updated to do so. Based on #1614 but updated to the current version, and with better fallback behaviour. * Closes #1611 * Supersedes #1614 --- crates/eframe/src/native/glow_integration.rs | 27 +++++++++++++++++++ crates/eframe/src/native/wgpu_integration.rs | 27 +++++++++++++++++++ crates/egui-winit/src/lib.rs | 7 +++++ crates/egui/src/data/input.rs | 6 +++++ crates/egui/src/input_state.rs | 20 ++++++++++++++ crates/egui/src/response.rs | 14 ++++++++++ .../egui_demo_app/src/apps/custom3d_glow.rs | 2 +- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 +- examples/custom_3d_glow/src/main.rs | 2 +- 9 files changed, 104 insertions(+), 3 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index be30d853..0cb7ec33 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -446,6 +446,33 @@ impl WinitApp for GlowWinitApp { } } + winit::event::Event::DeviceEvent { + device_id: _, + event: winit::event::DeviceEvent::MouseMotion { delta }, + } => { + if let Some(running) = &mut self.running { + let mut glutin = running.glutin.borrow_mut(); + if let Some(viewport) = glutin + .focused_viewport + .and_then(|viewport| glutin.viewports.get_mut(&viewport)) + { + if let Some(egui_winit) = viewport.egui_winit.as_mut() { + egui_winit.on_mouse_motion(*delta); + } + + if let Some(window) = viewport.window.as_ref() { + EventResult::RepaintNext(window.id()) + } else { + EventResult::Wait + } + } else { + EventResult::Wait + } + } else { + EventResult::Wait + } + } + #[cfg(feature = "accesskit")] winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest( accesskit_winit::ActionRequestEvent { request, window_id }, diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index e8fe39f9..b3451be9 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -456,6 +456,33 @@ impl WinitApp for WgpuWinitApp { } } + winit::event::Event::DeviceEvent { + device_id: _, + event: winit::event::DeviceEvent::MouseMotion { delta }, + } => { + if let Some(running) = &mut self.running { + let mut shared = running.shared.borrow_mut(); + if let Some(viewport) = shared + .focused_viewport + .and_then(|viewport| shared.viewports.get_mut(&viewport)) + { + if let Some(egui_winit) = viewport.egui_winit.as_mut() { + egui_winit.on_mouse_motion(*delta); + } + + if let Some(window) = viewport.window.as_ref() { + EventResult::RepaintNext(window.id()) + } else { + EventResult::Wait + } + } else { + EventResult::Wait + } + } else { + EventResult::Wait + } + } + #[cfg(feature = "accesskit")] winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest( accesskit_winit::ActionRequestEvent { request, window_id }, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 15b6663a..ca115082 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -475,6 +475,13 @@ impl State { } } + pub fn on_mouse_motion(&mut self, delta: (f64, f64)) { + self.egui_input.events.push(egui::Event::MouseMoved(Vec2 { + x: delta.0 as f32, + y: delta.1 as f32, + })); + } + /// Call this when there is a new [`accesskit::ActionRequest`]. /// /// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`]. diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 21fa269d..f61b9312 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -396,6 +396,12 @@ pub enum Event { /// The mouse or touch moved to a new place. PointerMoved(Pos2), + /// The mouse moved, the units are unspecified. + /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges. + /// `PointerMoved` and `MouseMoved` can be sent at the same time. + /// This event is optional. If the integration can not determine unfiltered motion it should not send this event. + MouseMoved(Vec2), + /// A mouse button was pressed or released (or a touch started or stopped). PointerButton { /// Where is the pointer? diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 0de78a43..5446a799 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -617,6 +617,11 @@ pub struct PointerState { /// How much the pointer moved compared to last frame, in points. delta: Vec2, + /// How much the mouse moved since the last frame, in unspecified units. + /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges. + /// May be unavailable on some integrations. + motion: Option, + /// Current velocity of pointer. velocity: Vec2, @@ -664,6 +669,7 @@ impl Default for PointerState { latest_pos: None, interact_pos: None, delta: Vec2::ZERO, + motion: None, velocity: Vec2::ZERO, pos_history: History::new(0..1000, 0.1), down: Default::default(), @@ -690,6 +696,9 @@ impl PointerState { let old_pos = self.latest_pos; self.interact_pos = self.latest_pos; + if self.motion.is_some() { + self.motion = Some(Vec2::ZERO); + } for event in &new.events { match event { @@ -775,6 +784,7 @@ impl PointerState { self.latest_pos = None; // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame. } + Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta, _ => {} } } @@ -819,6 +829,14 @@ impl PointerState { self.delta } + /// How much the mouse moved since the last frame, in unspecified units. + /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges. + /// May be unavailable on some integrations. + #[inline(always)] + pub fn motion(&self) -> Option { + self.motion + } + /// Current velocity of pointer. #[inline(always)] pub fn velocity(&self) -> Vec2 { @@ -1139,6 +1157,7 @@ impl PointerState { latest_pos, interact_pos, delta, + motion, velocity, pos_history: _, down, @@ -1155,6 +1174,7 @@ impl PointerState { ui.label(format!("latest_pos: {latest_pos:?}")); ui.label(format!("interact_pos: {interact_pos:?}")); ui.label(format!("delta: {delta:?}")); + ui.label(format!("motion: {motion:?}")); ui.label(format!( "velocity: [{:3.0} {:3.0}] points/sec", velocity.x, velocity.y diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 82349993..e63def16 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -356,6 +356,20 @@ impl Response { } } + /// If dragged, how far did the mouse move? + /// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`] + /// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen. + /// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter. + #[inline] + pub fn drag_motion(&self) -> Vec2 { + if self.dragged() { + self.ctx + .input(|i| i.pointer.motion().unwrap_or(i.pointer.delta())) + } else { + Vec2::ZERO + } + } + /// If the user started dragging this widget this frame, store the payload for drag-and-drop. #[doc(alias = "drag and drop")] pub fn dnd_set_drag_payload(&self, payload: Payload) { diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index 3175cf4f..7f488e76 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -55,7 +55,7 @@ impl Custom3d { let (rect, response) = ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); - self.angle += response.drag_delta().x * 0.01; + self.angle += response.drag_motion().x * 0.01; // Clone locals so we can move them into the paint callback: let angle = self.angle; diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 6b748cb1..1676a0ba 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -173,7 +173,7 @@ impl Custom3d { let (rect, response) = ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); - self.angle += response.drag_delta().x * 0.01; + self.angle += response.drag_motion().x * 0.01; ui.painter().add(egui_wgpu::Callback::new_paint_callback( rect, CustomTriangleCallback { angle: self.angle }, diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index 93797397..a1f6fa26 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -69,7 +69,7 @@ impl MyApp { let (rect, response) = ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); - self.angle += response.drag_delta().x * 0.01; + self.angle += response.drag_motion().x * 0.01; // Clone locals so we can move them into the paint callback: let angle = self.angle;