diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index 9b6ddb81..cb3c606c 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -264,7 +264,7 @@ pub enum WindowEvent { /// /// ## Platform-specific /// - /// - Only available on **macOS** and **iOS**. + /// - Only available on **macOS**, **iOS**, and **Wayland**. /// - On iOS, not recognized by default. It must be enabled when needed. PinchGesture { device_id: Option, @@ -280,7 +280,7 @@ pub enum WindowEvent { /// /// ## Platform-specific /// - /// - Only available on **iOS**. + /// - Only available on **iOS** and **Wayland**. /// - On iOS, not recognized by default. It must be enabled when needed. PanGesture { device_id: Option, @@ -316,7 +316,7 @@ pub enum WindowEvent { /// /// ## Platform-specific /// - /// - Only available on **macOS** and **iOS**. + /// - Only available on **macOS**, **iOS**, and **Wayland**. /// - On iOS, not recognized by default. It must be enabled when needed. RotationGesture { device_id: Option, diff --git a/winit-wayland/src/seat/mod.rs b/winit-wayland/src/seat/mod.rs index bf68d107..38f84ae1 100644 --- a/winit-wayland/src/seat/mod.rs +++ b/winit-wayland/src/seat/mod.rs @@ -12,6 +12,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3:: use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use tracing::warn; +use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1; use winit_core::event::WindowEvent; use winit_core::keyboard::ModifiersState; @@ -23,6 +24,7 @@ mod text_input; mod touch; use keyboard::{KeyboardData, KeyboardState}; +pub use pointer::pointer_gesture::{PointerGestureData, PointerGesturesState}; pub use pointer::relative_pointer::RelativePointerState; pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt}; use text_input::TextInputData; @@ -51,6 +53,9 @@ pub struct WinitSeatState { /// The relative pointer bound on the seat. relative_pointer: Option, + /// The pinch pointer gesture bound on the seat. + pointer_gesture_pinch: Option, + /// The keyboard bound on the seat. keyboard_state: Option, @@ -124,6 +129,14 @@ impl SeatHandler for WinitState { ) }); + seat_state.pointer_gesture_pinch = self.pointer_gestures.as_ref().map(|manager| { + manager.get_pinch_gesture( + themed_pointer.pointer(), + queue_handle, + PointerGestureData::default(), + ) + }); + let themed_pointer = Arc::new(themed_pointer); // Register cursor surface. @@ -177,6 +190,10 @@ impl SeatHandler for WinitState { relative_pointer.destroy(); } + if let Some(pointer_gesture_pinch) = seat_state.pointer_gesture_pinch.take() { + pointer_gesture_pinch.destroy(); + } + if let Some(pointer) = seat_state.pointer.take() { let pointer_data = pointer.pointer().winit_data(); diff --git a/winit-wayland/src/seat/pointer/mod.rs b/winit-wayland/src/seat/pointer/mod.rs index 9dea4b7f..3e7df4b6 100644 --- a/winit-wayland/src/seat/pointer/mod.rs +++ b/winit-wayland/src/seat/pointer/mod.rs @@ -36,6 +36,7 @@ use winit_core::event::{ use crate::state::WinitState; use crate::WindowId; +pub mod pointer_gesture; pub mod relative_pointer; impl PointerHandler for WinitState { diff --git a/winit-wayland/src/seat/pointer/pointer_gesture.rs b/winit-wayland/src/seat/pointer/pointer_gesture.rs new file mode 100644 index 00000000..d46f3706 --- /dev/null +++ b/winit-wayland/src/seat/pointer/pointer_gesture.rs @@ -0,0 +1,159 @@ +use std::ops::Deref; +use std::sync::Mutex; + +use dpi::{LogicalPosition, PhysicalPosition}; +use sctk::compositor::SurfaceData; +use sctk::globals::GlobalData; +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{ + Event, ZwpPointerGesturePinchV1, +}; +use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1; +use winit_core::event::{TouchPhase, WindowEvent}; +use winit_core::window::WindowId; + +use crate::state::WinitState; + +/// Wrapper around the pointer gesture. +#[derive(Debug)] +pub struct PointerGesturesState { + pointer_gestures: ZwpPointerGesturesV1, +} + +impl PointerGesturesState { + /// Create a new pointer gesture + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { pointer_gestures }) + } +} + +#[derive(Debug, Default)] +pub struct PointerGestureData { + inner: Mutex, +} + +#[derive(Debug)] +pub struct PointerGestureDataInner { + window_id: Option, + previous_pinch: f64, +} + +impl Default for PointerGestureDataInner { + fn default() -> Self { + Self { window_id: Default::default(), previous_pinch: 1.0 } + } +} + +impl Deref for PointerGesturesState { + type Target = ZwpPointerGesturesV1; + + fn deref(&self) -> &Self::Target { + &self.pointer_gestures + } +} + +impl Dispatch for PointerGesturesState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpPointerGesturesV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + unreachable!("zwp_pointer_gestures_v1 has no events") + } +} + +impl Dispatch for PointerGesturesState { + fn event( + state: &mut WinitState, + _proxy: &ZwpPointerGesturePinchV1, + event: ::Event, + data: &PointerGestureData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + let mut pointer_gesture_data = data.inner.lock().unwrap(); + let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event { + Event::Begin { surface, fingers, .. } => { + // We only support two fingers for now. + if fingers != 2 { + return; + } + + // Don't handle events from a subsurface. + if !surface + .data::() + .is_some_and(|data| data.parent_surface().is_none()) + { + return; + } + + let window_id = crate::make_wid(&surface); + + pointer_gesture_data.window_id = Some(window_id); + pointer_gesture_data.previous_pinch = 1.; + + (window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.) + }, + Event::Update { dx, dy, scale: pinch, rotation, .. } => { + let window_id = match pointer_gesture_data.window_id { + Some(window_id) => window_id, + _ => return, + }; + + let scale_factor = match state.windows.get_mut().get_mut(&window_id) { + Some(window) => window.lock().unwrap().scale_factor(), + None => return, + }; + + let pan_delta = + LogicalPosition::new(dx as f32, dy as f32).to_physical(scale_factor); + + let pinch_delta = pinch - pointer_gesture_data.previous_pinch; + pointer_gesture_data.previous_pinch = pinch; + + // Wayland provides rotation in degrees cw, opposite of winit's degrees ccw. + let rotation_delta = -rotation as f32; + (window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta) + }, + Event::End { cancelled, .. } => { + let window_id = match pointer_gesture_data.window_id { + Some(window_id) => window_id, + _ => return, + }; + + // Reset the state. + *pointer_gesture_data = Default::default(); + + let phase = if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled }; + (window_id, phase, PhysicalPosition::new(0., 0.), 0., 0.) + }, + _ => unreachable!("Unknown event {event:?}"), + }; + + // The chance of only one of these events being necessary is extremely small, + // so it is easier to just send all three + state.events_sink.push_window_event( + WindowEvent::PanGesture { device_id: None, delta: pan_delta, phase }, + window_id, + ); + state.events_sink.push_window_event( + WindowEvent::PinchGesture { device_id: None, delta: pinch_delta, phase }, + window_id, + ); + state.events_sink.push_window_event( + WindowEvent::RotationGesture { device_id: None, delta: rotation_delta, phase }, + window_id, + ); + } +} + +delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState); +delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState); diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 9f8a4f49..2ca1de86 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -25,8 +25,8 @@ use winit_core::error::OsError; use crate::event_loop::sink::EventSink; use crate::output::MonitorHandle; use crate::seat::{ - PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, - WinitPointerDataExt, WinitSeatState, + PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState, + WinitPointerData, WinitPointerDataExt, WinitSeatState, }; use crate::types::kwin_blur::KWinBlurManager; use crate::types::wp_fractional_scaling::FractionalScalingManager; @@ -103,6 +103,9 @@ pub struct WinitState { /// Pointer constraints to handle pointer locking and confining. pub pointer_constraints: Option>, + /// Pointer gestures to handle pinch, rotate, and pan + pub pointer_gestures: Option, + /// Viewporter state on the given window. pub viewporter_state: Option, @@ -195,6 +198,7 @@ impl WinitState { .map(Arc::new) .ok(), pointer_surfaces: Default::default(), + pointer_gestures: PointerGesturesState::new(globals, queue_handle).ok(), monitors: Arc::new(Mutex::new(monitors)), events_sink: EventSink::new(), diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 33aa4194..c59decc8 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -82,6 +82,7 @@ changelog entry. - `ActivationToken::as_raw` to get a ref to raw token. - Each platform now has corresponding `WindowAttributes` struct instead of trait extension. - On Wayland, added implementation for `Window::set_window_icon` +- On Wayland, added `PanGesture`, `PinchGesture`, and `RotationGesture` - Add `Window::request_ime_update` to atomically apply set of IME changes. - Add `Ime::DeleteSurrounding` to let the input method delete text. - Add more `ImePurpose` values.