diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index be30d853ccf..0cb7ec331f9 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 e8fe39f9064..b3451be9cb0 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 15b6663a7ba..ca115082216 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 21fa269dbf5..f61b9312f89 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 0de78a43752..5446a799f70 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 ff7c7da8f87..9d0767f84a5 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -336,6 +336,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 3175cf4faab..7f488e7671e 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 6b748cb1d74..1676a0ba70c 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 93797397880..a1f6fa26946 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;