Skip to content

Commit

Permalink
Add API for raw mouse motion (#4063)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
GiantBlargg authored Feb 20, 2024
1 parent 74891ca commit b804857
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 3 deletions.
27 changes: 27 additions & 0 deletions crates/eframe/src/native/glow_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
27 changes: 27 additions & 0 deletions crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
7 changes: 7 additions & 0 deletions crates/egui-winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down
6 changes: 6 additions & 0 deletions crates/egui/src/data/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
20 changes: 20 additions & 0 deletions crates/egui/src/input_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec2>,

/// Current velocity of pointer.
velocity: Vec2,

Expand Down Expand Up @@ -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(),
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
_ => {}
}
}
Expand Down Expand Up @@ -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<Vec2> {
self.motion
}

/// Current velocity of pointer.
#[inline(always)]
pub fn velocity(&self) -> Vec2 {
Expand Down Expand Up @@ -1139,6 +1157,7 @@ impl PointerState {
latest_pos,
interact_pos,
delta,
motion,
velocity,
pos_history: _,
down,
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Payload: Any + Send + Sync>(&self, payload: Payload) {
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_app/src/apps/custom3d_glow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_app/src/apps/custom3d_wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_3d_glow/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit b804857

Please sign in to comment.