Skip to content

Commit b804857

Browse files
authored
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
1 parent 74891ca commit b804857

9 files changed

Lines changed: 104 additions & 3 deletions

File tree

crates/eframe/src/native/glow_integration.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,33 @@ impl WinitApp for GlowWinitApp {
446446
}
447447
}
448448

449+
winit::event::Event::DeviceEvent {
450+
device_id: _,
451+
event: winit::event::DeviceEvent::MouseMotion { delta },
452+
} => {
453+
if let Some(running) = &mut self.running {
454+
let mut glutin = running.glutin.borrow_mut();
455+
if let Some(viewport) = glutin
456+
.focused_viewport
457+
.and_then(|viewport| glutin.viewports.get_mut(&viewport))
458+
{
459+
if let Some(egui_winit) = viewport.egui_winit.as_mut() {
460+
egui_winit.on_mouse_motion(*delta);
461+
}
462+
463+
if let Some(window) = viewport.window.as_ref() {
464+
EventResult::RepaintNext(window.id())
465+
} else {
466+
EventResult::Wait
467+
}
468+
} else {
469+
EventResult::Wait
470+
}
471+
} else {
472+
EventResult::Wait
473+
}
474+
}
475+
449476
#[cfg(feature = "accesskit")]
450477
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
451478
accesskit_winit::ActionRequestEvent { request, window_id },

crates/eframe/src/native/wgpu_integration.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,33 @@ impl WinitApp for WgpuWinitApp {
456456
}
457457
}
458458

459+
winit::event::Event::DeviceEvent {
460+
device_id: _,
461+
event: winit::event::DeviceEvent::MouseMotion { delta },
462+
} => {
463+
if let Some(running) = &mut self.running {
464+
let mut shared = running.shared.borrow_mut();
465+
if let Some(viewport) = shared
466+
.focused_viewport
467+
.and_then(|viewport| shared.viewports.get_mut(&viewport))
468+
{
469+
if let Some(egui_winit) = viewport.egui_winit.as_mut() {
470+
egui_winit.on_mouse_motion(*delta);
471+
}
472+
473+
if let Some(window) = viewport.window.as_ref() {
474+
EventResult::RepaintNext(window.id())
475+
} else {
476+
EventResult::Wait
477+
}
478+
} else {
479+
EventResult::Wait
480+
}
481+
} else {
482+
EventResult::Wait
483+
}
484+
}
485+
459486
#[cfg(feature = "accesskit")]
460487
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
461488
accesskit_winit::ActionRequestEvent { request, window_id },

crates/egui-winit/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,13 @@ impl State {
475475
}
476476
}
477477

478+
pub fn on_mouse_motion(&mut self, delta: (f64, f64)) {
479+
self.egui_input.events.push(egui::Event::MouseMoved(Vec2 {
480+
x: delta.0 as f32,
481+
y: delta.1 as f32,
482+
}));
483+
}
484+
478485
/// Call this when there is a new [`accesskit::ActionRequest`].
479486
///
480487
/// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].

crates/egui/src/data/input.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,12 @@ pub enum Event {
396396
/// The mouse or touch moved to a new place.
397397
PointerMoved(Pos2),
398398

399+
/// The mouse moved, the units are unspecified.
400+
/// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
401+
/// `PointerMoved` and `MouseMoved` can be sent at the same time.
402+
/// This event is optional. If the integration can not determine unfiltered motion it should not send this event.
403+
MouseMoved(Vec2),
404+
399405
/// A mouse button was pressed or released (or a touch started or stopped).
400406
PointerButton {
401407
/// Where is the pointer?

crates/egui/src/input_state.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,11 @@ pub struct PointerState {
617617
/// How much the pointer moved compared to last frame, in points.
618618
delta: Vec2,
619619

620+
/// How much the mouse moved since the last frame, in unspecified units.
621+
/// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
622+
/// May be unavailable on some integrations.
623+
motion: Option<Vec2>,
624+
620625
/// Current velocity of pointer.
621626
velocity: Vec2,
622627

@@ -664,6 +669,7 @@ impl Default for PointerState {
664669
latest_pos: None,
665670
interact_pos: None,
666671
delta: Vec2::ZERO,
672+
motion: None,
667673
velocity: Vec2::ZERO,
668674
pos_history: History::new(0..1000, 0.1),
669675
down: Default::default(),
@@ -690,6 +696,9 @@ impl PointerState {
690696

691697
let old_pos = self.latest_pos;
692698
self.interact_pos = self.latest_pos;
699+
if self.motion.is_some() {
700+
self.motion = Some(Vec2::ZERO);
701+
}
693702

694703
for event in &new.events {
695704
match event {
@@ -775,6 +784,7 @@ impl PointerState {
775784
self.latest_pos = None;
776785
// NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame.
777786
}
787+
Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta,
778788
_ => {}
779789
}
780790
}
@@ -819,6 +829,14 @@ impl PointerState {
819829
self.delta
820830
}
821831

832+
/// How much the mouse moved since the last frame, in unspecified units.
833+
/// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
834+
/// May be unavailable on some integrations.
835+
#[inline(always)]
836+
pub fn motion(&self) -> Option<Vec2> {
837+
self.motion
838+
}
839+
822840
/// Current velocity of pointer.
823841
#[inline(always)]
824842
pub fn velocity(&self) -> Vec2 {
@@ -1139,6 +1157,7 @@ impl PointerState {
11391157
latest_pos,
11401158
interact_pos,
11411159
delta,
1160+
motion,
11421161
velocity,
11431162
pos_history: _,
11441163
down,
@@ -1155,6 +1174,7 @@ impl PointerState {
11551174
ui.label(format!("latest_pos: {latest_pos:?}"));
11561175
ui.label(format!("interact_pos: {interact_pos:?}"));
11571176
ui.label(format!("delta: {delta:?}"));
1177+
ui.label(format!("motion: {motion:?}"));
11581178
ui.label(format!(
11591179
"velocity: [{:3.0} {:3.0}] points/sec",
11601180
velocity.x, velocity.y

crates/egui/src/response.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,20 @@ impl Response {
356356
}
357357
}
358358

359+
/// If dragged, how far did the mouse move?
360+
/// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`]
361+
/// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen.
362+
/// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter.
363+
#[inline]
364+
pub fn drag_motion(&self) -> Vec2 {
365+
if self.dragged() {
366+
self.ctx
367+
.input(|i| i.pointer.motion().unwrap_or(i.pointer.delta()))
368+
} else {
369+
Vec2::ZERO
370+
}
371+
}
372+
359373
/// If the user started dragging this widget this frame, store the payload for drag-and-drop.
360374
#[doc(alias = "drag and drop")]
361375
pub fn dnd_set_drag_payload<Payload: Any + Send + Sync>(&self, payload: Payload) {

crates/egui_demo_app/src/apps/custom3d_glow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl Custom3d {
5555
let (rect, response) =
5656
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
5757

58-
self.angle += response.drag_delta().x * 0.01;
58+
self.angle += response.drag_motion().x * 0.01;
5959

6060
// Clone locals so we can move them into the paint callback:
6161
let angle = self.angle;

crates/egui_demo_app/src/apps/custom3d_wgpu.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ impl Custom3d {
173173
let (rect, response) =
174174
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
175175

176-
self.angle += response.drag_delta().x * 0.01;
176+
self.angle += response.drag_motion().x * 0.01;
177177
ui.painter().add(egui_wgpu::Callback::new_paint_callback(
178178
rect,
179179
CustomTriangleCallback { angle: self.angle },

examples/custom_3d_glow/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl MyApp {
6969
let (rect, response) =
7070
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
7171

72-
self.angle += response.drag_delta().x * 0.01;
72+
self.angle += response.drag_motion().x * 0.01;
7373

7474
// Clone locals so we can move them into the paint callback:
7575
let angle = self.angle;

0 commit comments

Comments
 (0)