From da2cef97a3e63b8f73512b2c0c8d42020b7123e6 Mon Sep 17 00:00:00 2001 From: Joonas Satka Date: Tue, 16 Aug 2022 18:20:06 +0300 Subject: [PATCH] Add touchpad magnify and rotate gestures support for macOS (#2157) * Add touchpad magnify support for macOS * Add touchpad rotate support for macOS * Add macOS rotate and magnify gesture cancelled phases * Correct docs for TouchpadRotate event * Fix tracing macros --- CHANGELOG.md | 1 + examples/touchpad_gestures.rs | 43 +++++++++++++++++++++ src/event.rs | 64 ++++++++++++++++++++++++++++++++ src/platform_impl/macos/view.rs | 66 +++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 examples/touchpad_gestures.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bae9bd74..6c21be0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window. - On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled. +- On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. # 0.27.2 (2022-8-12) diff --git a/examples/touchpad_gestures.rs b/examples/touchpad_gestures.rs new file mode 100644 index 00000000..85d9e967 --- /dev/null +++ b/examples/touchpad_gestures.rs @@ -0,0 +1,43 @@ +use simple_logger::SimpleLogger; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("Touchpad gestures") + .build(&event_loop) + .unwrap(); + + println!("Only supported on macOS at the moment."); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { event, .. } = event { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::TouchpadMagnify { delta, .. } => { + if delta > 0.0 { + println!("Zoomed in {}", delta); + } else { + println!("Zoomed out {}", delta); + } + } + WindowEvent::TouchpadRotate { delta, .. } => { + if delta > 0.0 { + println!("Rotated counterclockwise {}", delta); + } else { + println!("Rotated clockwise {}", delta); + } + } + _ => (), + } + } + }); +} diff --git a/src/event.rs b/src/event.rs index 25cae732..d54b1b6c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -426,6 +426,34 @@ pub enum WindowEvent<'a> { modifiers: ModifiersState, }, + /// Touchpad magnification event with two-finger pinch gesture. + /// + /// Positive delta values indicate magnification (zooming in) and + /// negative delta values indicate shrinking (zooming out). + /// + /// ## Platform-specific + /// + /// - Only available on **macOS**. + TouchpadMagnify { + device_id: DeviceId, + delta: f64, + phase: TouchPhase, + }, + + /// Touchpad rotation event with two-finger rotation gesture. + /// + /// Positive delta values indicate rotation counterclockwise and + /// negative delta values indicate rotation clockwise. + /// + /// ## Platform-specific + /// + /// - Only available on **macOS**. + TouchpadRotate { + device_id: DeviceId, + delta: f32, + phase: TouchPhase, + }, + /// Touchpad pressure event. /// /// At the moment, only supported on Apple forcetouch-capable macbooks. @@ -549,6 +577,24 @@ impl Clone for WindowEvent<'static> { button: *button, modifiers: *modifiers, }, + TouchpadMagnify { + device_id, + delta, + phase, + } => TouchpadMagnify { + device_id: *device_id, + delta: *delta, + phase: *phase, + }, + TouchpadRotate { + device_id, + delta, + phase, + } => TouchpadRotate { + device_id: *device_id, + delta: *delta, + phase: *phase, + }, TouchpadPressure { device_id, pressure, @@ -637,6 +683,24 @@ impl<'a> WindowEvent<'a> { button, modifiers, }), + TouchpadMagnify { + device_id, + delta, + phase, + } => Some(TouchpadMagnify { + device_id, + delta, + phase, + }), + TouchpadRotate { + device_id, + delta, + phase, + } => Some(TouchpadRotate { + device_id, + delta, + phase, + }), TouchpadPressure { device_id, pressure, diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 1b40d76d..16bc457e 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -301,6 +301,14 @@ static VIEW_CLASS: Lazy = Lazy::new(|| unsafe { sel!(scrollWheel:), scroll_wheel as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(magnifyWithEvent:), + magnify_with_event as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rotateWithEvent:), + rotate_with_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(pressureChangeWithEvent:), pressure_change_with_event as extern "C" fn(&Object, Sel, id), @@ -1196,6 +1204,64 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { } } +extern "C" fn magnify_with_event(this: &Object, _sel: Sel, event: id) { + trace_scope!("magnifyWithEvent:"); + + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let delta = event.magnification(); + let phase = match event.phase() { + NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, + NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => return, + }; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::TouchpadMagnify { + device_id: DEVICE_ID, + delta, + phase, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } +} + +extern "C" fn rotate_with_event(this: &Object, _sel: Sel, event: id) { + trace_scope!("rotateWithEvent:"); + + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let delta = event.rotation(); + let phase = match event.phase() { + NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, + NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => return, + }; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::TouchpadRotate { + device_id: DEVICE_ID, + delta, + phase, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } +} + extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { trace_scope!("pressureChangeWithEvent:");