Skip to content

Commit

Permalink
wayland: Add support for PinchGesture and RotationGesture
Browse files Browse the repository at this point in the history
These two events are synthesized from the same event from the same
protocol, zwp_pointer_gestures_v1, and will always come together.

I’ve left over the third value, which is a dx/dy from the center of the
gesture, it could be exposed as a 2D scroll but I feel like this should
be a different event than the normal AxisMotion.

The documentation doesn’t indicate the unit of the rotation, so I’ve
left it as degrees.  I don’t have any Apple OS where I could test
whether that event is also in degrees or in radians there.

The other two gestures supported by this protocol, a multi-finger swipe,
and a multi-finger hold, aren’t currently matched with any event in
winit, would it make sense to expose them?
  • Loading branch information
linkmauve committed Apr 27, 2024
1 parent 1682703 commit 01977f7
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ changelog entry.
Windows, macOS, X11, Wayland, and Web.
- On Web, add to toggle calling `Event.preventDefault()` on `Window`.
- On iOS, add `PinchGesture`, `DoubleTapGesture`, and `RotationGesture`
- On Wayland, add `PinchGesture` and `RotationGesture`
- On macOS, add services menu.
- On Windows, add `with_title_text_color`, and `with_corner_preference` on
`WindowAttributesExtWindows`.
Expand Down
4 changes: 2 additions & 2 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,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: DeviceId,
Expand Down Expand Up @@ -320,7 +320,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: DeviceId, delta: f32, phase: TouchPhase },

Expand Down
16 changes: 15 additions & 1 deletion src/platform_impl/linux/wayland/seat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;

Expand All @@ -24,7 +25,9 @@ mod text_input;
mod touch;

pub use pointer::relative_pointer::RelativePointerState;
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
pub use pointer::{
PointerConstraintsState, PointerGesturesState, WinitPointerData, WinitPointerDataExt,
};
pub use text_input::{TextInputState, ZwpTextInputV3Ext};

use keyboard::{KeyboardData, KeyboardState};
Expand All @@ -48,6 +51,9 @@ pub struct WinitSeatState {
/// The relative pointer bound on the seat.
relative_pointer: Option<ZwpRelativePointerV1>,

/// The pinch gesture of the pointer bound on the seat.
pinch_gesture: Option<ZwpPointerGesturePinchV1>,

/// The keyboard bound on the seat.
keyboard_state: Option<KeyboardState>,

Expand Down Expand Up @@ -111,6 +117,14 @@ impl SeatHandler for WinitState {
)
});

seat_state.pinch_gesture = self.pointer_gestures.as_ref().map(|gestures| {
gestures.get_pinch_gesture(
themed_pointer.pointer(),
queue_handle,
sctk::globals::GlobalData,
)
});

let themed_pointer = Arc::new(themed_pointer);

// Register cursor surface.
Expand Down
88 changes: 88 additions & 0 deletions src/platform_impl/linux/wayland/seat/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1;
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{ZwpPointerGesturePinchV1, Event};
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::csd_frame::FrameClick;

Expand Down Expand Up @@ -467,9 +469,95 @@ impl Dispatch<WpCursorShapeManagerV1, GlobalData, WinitState> for SeatState {
}
}

pub struct PointerGesturesState {
pointer_gestures: ZwpPointerGesturesV1,
}

impl PointerGesturesState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { pointer_gestures })
}
}

impl Deref for PointerGesturesState {
type Target = ZwpPointerGesturesV1;

fn deref(&self) -> &Self::Target {
&self.pointer_gestures
}
}

impl Dispatch<ZwpPointerGesturesV1, GlobalData, WinitState> for PointerGesturesState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpPointerGesturesV1,
_event: <ZwpPointerGesturesV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
unreachable!("zwp_pointer_gestures_v1 has no events")
}
}

impl Dispatch<ZwpPointerGesturePinchV1, GlobalData, WinitState> for PointerGesturesState {
fn event(
state: &mut WinitState,
_proxy: &ZwpPointerGesturePinchV1,
event: <ZwpPointerGesturePinchV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
let (phase, scale_delta, rotation_delta) = match event {
Event::Begin { time: _, serial: _, surface, fingers } => {
if fingers != 2 {
// We only support two fingers for now.
return;
}
// The parent surface.
let parent_surface = match surface.data::<SurfaceData>() {
Some(data) => data.parent_surface().unwrap_or(&surface),
None => return,
};

state.window_id = wayland::make_wid(parent_surface);
state.previous_scale = 1.;

(TouchPhase::Started, 0., 0.)
},
Event::Update { time: _, dx: _, dy: _, scale, rotation } => {
let scale_delta = scale - state.previous_scale;
state.previous_scale = scale;
(TouchPhase::Moved, scale_delta, rotation as f32)
},
Event::End { time: _, serial: _, cancelled } => {
state.previous_scale = 1.;
(if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled }, 0., 0.)
},
_ => unreachable!("Unknown event {event:?}"),
};
state.events_sink.push_window_event(
WindowEvent::PinchGesture { device_id, delta: scale_delta, phase },
state.window_id,
);
state.events_sink.push_window_event(
WindowEvent::RotationGesture { device_id, delta: rotation_delta, phase },
state.window_id,
);
}
}

delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState);
delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState);
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: GlobalData] => PointerGesturesState);
17 changes: 15 additions & 2 deletions src/platform_impl/linux/wayland/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use sctk::subcompositor::SubcompositorState;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::output::MonitorHandle;
use crate::platform_impl::wayland::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState,
WinitPointerData, WinitPointerDataExt, WinitSeatState,
};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
Expand Down Expand Up @@ -100,6 +100,15 @@ pub struct WinitState {
/// Pointer constraints to handle pointer locking and confining.
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,

/// Pointer gestures to handle swipe, pinch and hold.
pub pointer_gestures: Option<PointerGesturesState>,

/// XXX: Is this really meant to stay here?
pub window_id: WindowId,

/// XXX: Is this really meant to stay here?
pub previous_scale: f64,

/// Viewporter state on the given window.
pub viewporter_state: Option<ViewporterState>,

Expand Down Expand Up @@ -185,8 +194,12 @@ impl WinitState {
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
.map(Arc::new)
.ok(),
pointer_gestures: PointerGesturesState::new(globals, queue_handle).ok(),
pointer_surfaces: Default::default(),

window_id: WindowId(0),
previous_scale: 1.,

monitors: Arc::new(Mutex::new(monitors)),
events_sink: EventSink::new(),
loop_handle,
Expand Down

0 comments on commit 01977f7

Please sign in to comment.