Mouse events (#344)

* Explicit mouse-related DeviceEvents

This makes the API more intuitive for common use-cases and allows us
to better propagate platform knowledge of motion semantics.

* Improve event naming consistency

* Clarify axis event forwards-compatibility

* Rename WindowEvent::MouseMoved/Entered/Left to CursorMoved/...

This emphasizes the difference between motion of the host GUI cursor,
as used for clicking on things, and raw mouse(-like) input data, as
used for first-person controls.

* Add support for windows and OSX, fix merging

* Fix warnings and errors on Linux

* Remove unnecessary breaking changes

* Add MouseWheel events to windows and OSX

* Fix bad push call.

* Fix docs, naming, and x11 events

* Remove mutability warning

* Add changelog entry
This commit is contained in:
Jacob Kiesel 2017-11-12 13:56:57 -07:00 committed by Victor Berger
parent c61f9b75f8
commit cfd087d9a5
8 changed files with 117 additions and 41 deletions

View file

@ -15,6 +15,9 @@
- Impl `Clone` for `EventsLoopProxy` - Impl `Clone` for `EventsLoopProxy`
- `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found - `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found
- Support for touch event on wayland - Support for touch event on wayland
- `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
- New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`.
# Version 0.8.3 (2017-10-11) # Version 0.8.3 (2017-10-11)

View file

@ -30,7 +30,7 @@ fn main() {
WindowEvent::Closed => return ControlFlow::Break, WindowEvent::Closed => return ControlFlow::Break,
a @ WindowEvent::MouseMoved { .. } => { a @ WindowEvent::CursorMoved { .. } => {
println!("{:?}", a); println!("{:?}", a);
}, },

View file

@ -54,17 +54,20 @@ pub enum WindowEvent {
KeyboardInput { device_id: DeviceId, input: KeyboardInput }, KeyboardInput { device_id: DeviceId, input: KeyboardInput },
/// The cursor has moved on the window. /// The cursor has moved on the window.
/// CursorMoved {
/// `position` is (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this device_id: DeviceId,
/// data is limited by the display area and it may have been transformed by the OS to implement effects such as
/// mouse acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
MouseMoved { device_id: DeviceId, position: (f64, f64) }, /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
position: (f64, f64),
},
/// The cursor has entered the window. /// The cursor has entered the window.
MouseEntered { device_id: DeviceId }, CursorEntered { device_id: DeviceId },
/// The cursor has left the window. /// The cursor has left the window.
MouseLeft { device_id: DeviceId }, CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred. /// A mouse wheel movement or touchpad scroll occurred.
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase }, MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase },
@ -79,7 +82,7 @@ pub enum WindowEvent {
/// is being pressed) and stage (integer representing the click level). /// is being pressed) and stage (integer representing the click level).
TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 }, TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
/// Motion on some analog axis not otherwise handled. May overlap with mouse motion. /// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 }, AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
/// The window needs to be redrawn. /// The window needs to be redrawn.
@ -110,10 +113,27 @@ pub enum WindowEvent {
pub enum DeviceEvent { pub enum DeviceEvent {
Added, Added,
Removed, Removed,
/// Mouse devices yield `Motion` events where axis `0` is horizontal and axis `1` is vertical.
/// A positive value means a movement to the right or the bottom, depending on the axis. /// Change in physical position of a pointing device.
/// Such events will be sent even if the mouse is in a corner of the screen. ///
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
MouseMotion {
/// (x, y) change in position in unspecified units.
///
/// Different devices may use different units.
delta: (f64, f64),
},
/// Physical scroll event
MouseWheel {
delta: MouseScrollDelta,
},
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// that winit supports on this platform, including mouse devices. If the device is a mouse
/// device then this will be reported alongside the MouseMotion event.
Motion { axis: AxisId, value: f64 }, Motion { axis: AxisId, value: f64 },
Button { button: ButtonId, state: ElementState }, Button { button: ButtonId, state: ElementState },
Key(KeyboardInput), Key(KeyboardInput),
Text { codepoint: char }, Text { codepoint: char },

View file

@ -41,13 +41,13 @@ pub fn pointer_implementation() -> wl_pointer::Implementation<PointerIData> {
idata.mouse_focus = Some(wid); idata.mouse_focus = Some(wid);
let mut guard = idata.sink.lock().unwrap(); let mut guard = idata.sink.lock().unwrap();
guard.send_event( guard.send_event(
Event::MouseEntered { Event::CursorEntered {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
}, },
wid, wid,
); );
guard.send_event( guard.send_event(
Event::MouseMoved { Event::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (x, y), position: (x, y),
}, },
@ -61,7 +61,7 @@ pub fn pointer_implementation() -> wl_pointer::Implementation<PointerIData> {
if let Some(wid) = wid { if let Some(wid) = wid {
let mut guard = idata.sink.lock().unwrap(); let mut guard = idata.sink.lock().unwrap();
guard.send_event( guard.send_event(
Event::MouseLeft { Event::CursorLeft {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
}, },
wid, wid,
@ -71,7 +71,7 @@ pub fn pointer_implementation() -> wl_pointer::Implementation<PointerIData> {
motion: |_, idata, _, _, x, y| { motion: |_, idata, _, _, x, y| {
if let Some(wid) = idata.mouse_focus { if let Some(wid) = idata.mouse_focus {
idata.sink.lock().unwrap().send_event( idata.sink.lock().unwrap().send_event(
Event::MouseMoved { Event::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (x, y) position: (x, y)
}, },

View file

@ -342,7 +342,7 @@ impl EventsLoop {
return; return;
} }
use events::WindowEvent::{Focused, MouseEntered, MouseInput, MouseLeft, MouseMoved, MouseWheel, AxisMotion}; use events::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion};
use events::ElementState::{Pressed, Released}; use events::ElementState::{Pressed, Released};
use events::MouseButton::{Left, Right, Middle, Other}; use events::MouseButton::{Left, Right, Middle, Other};
use events::MouseScrollDelta::LineDelta; use events::MouseScrollDelta::LineDelta;
@ -405,7 +405,7 @@ impl EventsLoop {
true true
} else { false } } else { false }
} { } {
callback(Event::WindowEvent { window_id: wid, event: MouseMoved { callback(Event::WindowEvent { window_id: wid, event: CursorMoved {
device_id: did, device_id: did,
position: new_cursor_pos position: new_cursor_pos
}}); }});
@ -421,9 +421,10 @@ impl EventsLoop {
let mut value = xev.valuators.values; let mut value = xev.valuators.values;
for i in 0..xev.valuators.mask_len*8 { for i in 0..xev.valuators.mask_len*8 {
if ffi::XIMaskIsSet(mask, i) { if ffi::XIMaskIsSet(mask, i) {
let x = unsafe { *value };
if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) { if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) {
let delta = (unsafe { *value } - info.position) / info.increment; let delta = (x - info.position) / info.increment;
info.position = unsafe { *value }; info.position = x;
events.push(Event::WindowEvent { window_id: wid, event: MouseWheel { events.push(Event::WindowEvent { window_id: wid, event: MouseWheel {
device_id: did, device_id: did,
delta: match info.orientation { delta: match info.orientation {
@ -460,11 +461,11 @@ impl EventsLoop {
} }
} }
callback(Event::WindowEvent { window_id: mkwid(xev.event), event: MouseEntered { device_id: mkdid(xev.deviceid) } }) callback(Event::WindowEvent { window_id: mkwid(xev.event), event: CursorEntered { device_id: mkdid(xev.deviceid) } })
} }
ffi::XI_Leave => { ffi::XI_Leave => {
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
callback(Event::WindowEvent { window_id: mkwid(xev.event), event: MouseLeft { device_id: mkdid(xev.deviceid) } }) callback(Event::WindowEvent { window_id: mkwid(xev.event), event: CursorLeft { device_id: mkdid(xev.deviceid) } })
} }
ffi::XI_FocusIn => { ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
@ -521,16 +522,38 @@ impl EventsLoop {
let did = mkdid(xev.deviceid); let did = mkdid(xev.deviceid);
let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
let mut value = xev.valuators.values; let mut value = xev.raw_values;
let mut mouse_delta = (0.0, 0.0);
let mut scroll_delta = (0.0, 0.0);
for i in 0..xev.valuators.mask_len*8 { for i in 0..xev.valuators.mask_len*8 {
if ffi::XIMaskIsSet(mask, i) { if ffi::XIMaskIsSet(mask, i) {
let x = unsafe { *value };
// We assume that every XInput2 device with analog axes is a pointing device emitting
// relative coordinates.
match i {
0 => mouse_delta.0 = x,
1 => mouse_delta.1 = x,
2 => scroll_delta.0 = x as f32,
3 => scroll_delta.1 = x as f32,
_ => {},
}
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion { callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion {
axis: i as u32, axis: i as u32,
value: unsafe { *value }, value: x,
}}); }});
value = unsafe { value.offset(1) }; value = unsafe { value.offset(1) };
} }
} }
if mouse_delta != (0.0, 0.0) {
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseMotion {
delta: mouse_delta,
}});
}
if scroll_delta != (0.0, 0.0) {
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseWheel {
delta: LineDelta(scroll_delta.0, scroll_delta.1),
}});
}
} }
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
@ -736,7 +759,7 @@ impl Window {
if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) { if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) {
let nspot = ffi::XPoint{x: x, y: y}; let nspot = ffi::XPoint{x: x, y: y};
let mut windows = windows.lock().unwrap(); let mut windows = windows.lock().unwrap();
let mut w = windows.get_mut(&self.window.id()).unwrap(); let w = windows.get_mut(&self.window.id()).unwrap();
if w.ic_spot.x == x && w.ic_spot.y == y { if w.ic_spot.x == x && w.ic_spot.y == y {
return return
} }

View file

@ -559,7 +559,7 @@ impl Window2 {
MouseCursor::NotAllowed => load("crossed_circle"), MouseCursor::NotAllowed => load("crossed_circle"),
/// Resize cursors // Resize cursors
MouseCursor::EResize => load("right_side"), MouseCursor::EResize => load("right_side"),
MouseCursor::NResize => load("top_side"), MouseCursor::NResize => load("top_side"),
MouseCursor::NeResize => load("top_right_corner"), MouseCursor::NeResize => load("top_right_corner"),

View file

@ -442,8 +442,8 @@ impl EventsLoop {
appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle })) }, appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle })) },
appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Middle })) }, appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Middle })) },
appkit::NSMouseEntered => { Some(into_event(WindowEvent::MouseEntered { device_id: DEVICE_ID })) }, appkit::NSMouseEntered => { Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID })) },
appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft { device_id: DEVICE_ID })) }, appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) },
appkit::NSMouseMoved | appkit::NSMouseMoved |
appkit::NSLeftMouseDragged | appkit::NSLeftMouseDragged |
@ -474,7 +474,7 @@ impl EventsLoop {
{ {
let x = (scale_factor * view_point.x as f32) as f64; let x = (scale_factor * view_point.x as f32) as f64;
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64; let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
let window_event = WindowEvent::MouseMoved { device_id: DEVICE_ID, position: (x, y) }; let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y) };
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event }; let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
events.push_back(event); events.push_back(event);
} }
@ -493,6 +493,12 @@ impl EventsLoop {
events.push_back(event); events.push_back(event);
} }
if delta_x != 0.0 || delta_y != 0.0 {
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let event = events.pop_front(); let event = events.pop_front();
self.shared.pending_events.lock().unwrap().extend(events.into_iter()); self.shared.pending_events.lock().unwrap().extend(events.into_iter());
event event
@ -519,7 +525,19 @@ impl EventsLoop {
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
_ => TouchPhase::Moved, _ => TouchPhase::Moved,
}; };
let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase }; self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel {
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta(ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32)
} else {
LineDelta(ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32)
},
}
});
let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta, phase };
Some(into_event(window_event)) Some(into_event(window_event))
}, },

View file

@ -401,7 +401,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
} }
winapi::WM_MOUSEMOVE => { winapi::WM_MOUSEMOVE => {
use events::WindowEvent::{MouseEntered, MouseMoved}; use events::WindowEvent::{CursorEntered, CursorMoved};
let mouse_outside_window = CONTEXT_STASH.with(|context_stash| { let mouse_outside_window = CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut(); let mut context_stash = context_stash.borrow_mut();
if let Some(context_stash) = context_stash.as_mut() { if let Some(context_stash) = context_stash.as_mut() {
@ -420,7 +420,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
if mouse_outside_window { if mouse_outside_window {
send_event(Event::WindowEvent { send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)), window_id: SuperWindowId(WindowId(window)),
event: MouseEntered { device_id: DEVICE_ID }, event: CursorEntered { device_id: DEVICE_ID },
}); });
// Calling TrackMouseEvent in order to receive mouse leave events. // Calling TrackMouseEvent in order to receive mouse leave events.
@ -437,14 +437,14 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
send_event(Event::WindowEvent { send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)), window_id: SuperWindowId(WindowId(window)),
event: MouseMoved { device_id: DEVICE_ID, position: (x, y) }, event: CursorMoved { device_id: DEVICE_ID, position: (x, y) },
}); });
0 0
}, },
winapi::WM_MOUSELEAVE => { winapi::WM_MOUSELEAVE => {
use events::WindowEvent::MouseLeft; use events::WindowEvent::CursorLeft;
let mouse_in_window = CONTEXT_STASH.with(|context_stash| { let mouse_in_window = CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut(); let mut context_stash = context_stash.borrow_mut();
if let Some(context_stash) = context_stash.as_mut() { if let Some(context_stash) = context_stash.as_mut() {
@ -463,7 +463,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
if mouse_in_window { if mouse_in_window {
send_event(Event::WindowEvent { send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)), window_id: SuperWindowId(WindowId(window)),
event: MouseLeft { device_id: DEVICE_ID } event: CursorLeft { device_id: DEVICE_ID }
}); });
} }
@ -471,7 +471,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
}, },
winapi::WM_MOUSEWHEEL => { winapi::WM_MOUSEWHEEL => {
use events::WindowEvent::MouseWheel; use events::{DeviceEvent, WindowEvent};
use events::MouseScrollDelta::LineDelta; use events::MouseScrollDelta::LineDelta;
use events::TouchPhase; use events::TouchPhase;
@ -481,7 +481,12 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
send_event(Event::WindowEvent { send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)), window_id: SuperWindowId(WindowId(window)),
event: MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved }, event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved },
});
send_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel { delta: LineDelta(0.0, value) },
}); });
0 0
@ -618,7 +623,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
}, },
winapi::WM_INPUT => { winapi::WM_INPUT => {
use events::DeviceEvent::Motion; use events::DeviceEvent::{Motion, MouseMotion};
let mut data: winapi::RAWINPUT = mem::uninitialized(); let mut data: winapi::RAWINPUT = mem::uninitialized();
let mut data_size = mem::size_of::<winapi::RAWINPUT>() as winapi::UINT; let mut data_size = mem::size_of::<winapi::RAWINPUT>() as winapi::UINT;
user32::GetRawInputData(mem::transmute(lparam), winapi::RID_INPUT, user32::GetRawInputData(mem::transmute(lparam), winapi::RID_INPUT,
@ -643,6 +648,13 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
event: Motion { axis: 1, value: y } event: Motion { axis: 1, value: y }
}); });
} }
if x != 0.0 || y != 0.0 {
send_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: MouseMotion { delta: (x, y) }
});
}
} }
0 0