Browse Source

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
pull/4085/head
Giantblargg 9 months ago
committed by GitHub
parent
commit
b8048572e8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 27
      crates/eframe/src/native/glow_integration.rs
  2. 27
      crates/eframe/src/native/wgpu_integration.rs
  3. 7
      crates/egui-winit/src/lib.rs
  4. 6
      crates/egui/src/data/input.rs
  5. 20
      crates/egui/src/input_state.rs
  6. 14
      crates/egui/src/response.rs
  7. 2
      crates/egui_demo_app/src/apps/custom3d_glow.rs
  8. 2
      crates/egui_demo_app/src/apps/custom3d_wgpu.rs
  9. 2
      examples/custom_3d_glow/src/main.rs

27
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 },

27
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 },

7
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`].

6
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?

20
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<Vec2>,
/// 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<Vec2> {
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

14
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<Payload: Any + Send + Sync>(&self, payload: Payload) {

2
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;

2
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 },

2
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;

Loading…
Cancel
Save