Skip to content

Commit 3be30af

Browse files
thatcomputerguy0101linkmauvekchibisov
authored
wayland: support for pinch, rotation, and pan gestures
Co-Authored-By: linkmauve <linkmauve@linkmauve.fr> Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
1 parent a68f1a6 commit 3be30af

File tree

6 files changed

+187
-5
lines changed

6 files changed

+187
-5
lines changed

winit-core/src/event.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ pub enum WindowEvent {
264264
///
265265
/// ## Platform-specific
266266
///
267-
/// - Only available on **macOS** and **iOS**.
267+
/// - Only available on **macOS**, **iOS**, and **Wayland**.
268268
/// - On iOS, not recognized by default. It must be enabled when needed.
269269
PinchGesture {
270270
device_id: Option<DeviceId>,
@@ -280,7 +280,7 @@ pub enum WindowEvent {
280280
///
281281
/// ## Platform-specific
282282
///
283-
/// - Only available on **iOS**.
283+
/// - Only available on **iOS** and **Wayland**.
284284
/// - On iOS, not recognized by default. It must be enabled when needed.
285285
PanGesture {
286286
device_id: Option<DeviceId>,
@@ -316,7 +316,7 @@ pub enum WindowEvent {
316316
///
317317
/// ## Platform-specific
318318
///
319-
/// - Only available on **macOS** and **iOS**.
319+
/// - Only available on **macOS**, **iOS**, and **Wayland**.
320320
/// - On iOS, not recognized by default. It must be enabled when needed.
321321
RotationGesture {
322322
device_id: Option<DeviceId>,

winit-wayland/src/seat/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
1212
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
1313
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
1414
use tracing::warn;
15+
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
1516
use winit_core::event::WindowEvent;
1617
use winit_core::keyboard::ModifiersState;
1718

@@ -23,6 +24,7 @@ mod text_input;
2324
mod touch;
2425

2526
use keyboard::{KeyboardData, KeyboardState};
27+
pub use pointer::pointer_gesture::{PointerGestureData, PointerGesturesState};
2628
pub use pointer::relative_pointer::RelativePointerState;
2729
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
2830
use text_input::TextInputData;
@@ -51,6 +53,9 @@ pub struct WinitSeatState {
5153
/// The relative pointer bound on the seat.
5254
relative_pointer: Option<ZwpRelativePointerV1>,
5355

56+
/// The pinch pointer gesture bound on the seat.
57+
pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>,
58+
5459
/// The keyboard bound on the seat.
5560
keyboard_state: Option<KeyboardState>,
5661

@@ -124,6 +129,14 @@ impl SeatHandler for WinitState {
124129
)
125130
});
126131

132+
seat_state.pointer_gesture_pinch = self.pointer_gestures.as_ref().map(|manager| {
133+
manager.get_pinch_gesture(
134+
themed_pointer.pointer(),
135+
queue_handle,
136+
PointerGestureData::default(),
137+
)
138+
});
139+
127140
let themed_pointer = Arc::new(themed_pointer);
128141

129142
// Register cursor surface.
@@ -177,6 +190,10 @@ impl SeatHandler for WinitState {
177190
relative_pointer.destroy();
178191
}
179192

193+
if let Some(pointer_gesture_pinch) = seat_state.pointer_gesture_pinch.take() {
194+
pointer_gesture_pinch.destroy();
195+
}
196+
180197
if let Some(pointer) = seat_state.pointer.take() {
181198
let pointer_data = pointer.pointer().winit_data();
182199

winit-wayland/src/seat/pointer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use winit_core::event::{
3636
use crate::state::WinitState;
3737
use crate::WindowId;
3838

39+
pub mod pointer_gesture;
3940
pub mod relative_pointer;
4041

4142
impl PointerHandler for WinitState {
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use std::ops::Deref;
2+
use std::sync::Mutex;
3+
4+
use dpi::{LogicalPosition, PhysicalPosition};
5+
use sctk::compositor::SurfaceData;
6+
use sctk::globals::GlobalData;
7+
use sctk::reexports::client::globals::{BindError, GlobalList};
8+
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
9+
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{
10+
Event, ZwpPointerGesturePinchV1,
11+
};
12+
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1;
13+
use winit_core::event::{TouchPhase, WindowEvent};
14+
use winit_core::window::WindowId;
15+
16+
use crate::state::WinitState;
17+
18+
/// Wrapper around the pointer gesture.
19+
#[derive(Debug)]
20+
pub struct PointerGesturesState {
21+
pointer_gestures: ZwpPointerGesturesV1,
22+
}
23+
24+
impl PointerGesturesState {
25+
/// Create a new pointer gesture
26+
pub fn new(
27+
globals: &GlobalList,
28+
queue_handle: &QueueHandle<WinitState>,
29+
) -> Result<Self, BindError> {
30+
let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?;
31+
Ok(Self { pointer_gestures })
32+
}
33+
}
34+
35+
#[derive(Debug, Default)]
36+
pub struct PointerGestureData {
37+
inner: Mutex<PointerGestureDataInner>,
38+
}
39+
40+
#[derive(Debug)]
41+
pub struct PointerGestureDataInner {
42+
window_id: Option<WindowId>,
43+
previous_pinch: f64,
44+
}
45+
46+
impl Default for PointerGestureDataInner {
47+
fn default() -> Self {
48+
Self { window_id: Default::default(), previous_pinch: 1.0 }
49+
}
50+
}
51+
52+
impl Deref for PointerGesturesState {
53+
type Target = ZwpPointerGesturesV1;
54+
55+
fn deref(&self) -> &Self::Target {
56+
&self.pointer_gestures
57+
}
58+
}
59+
60+
impl Dispatch<ZwpPointerGesturesV1, GlobalData, WinitState> for PointerGesturesState {
61+
fn event(
62+
_state: &mut WinitState,
63+
_proxy: &ZwpPointerGesturesV1,
64+
_event: <ZwpPointerGesturesV1 as wayland_client::Proxy>::Event,
65+
_data: &GlobalData,
66+
_conn: &Connection,
67+
_qhandle: &QueueHandle<WinitState>,
68+
) {
69+
unreachable!("zwp_pointer_gestures_v1 has no events")
70+
}
71+
}
72+
73+
impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState {
74+
fn event(
75+
state: &mut WinitState,
76+
_proxy: &ZwpPointerGesturePinchV1,
77+
event: <ZwpPointerGesturePinchV1 as Proxy>::Event,
78+
data: &PointerGestureData,
79+
_conn: &Connection,
80+
_qhandle: &QueueHandle<WinitState>,
81+
) {
82+
let mut pointer_gesture_data = data.inner.lock().unwrap();
83+
let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event {
84+
Event::Begin { surface, fingers, .. } => {
85+
// We only support two fingers for now.
86+
if fingers != 2 {
87+
return;
88+
}
89+
90+
// Don't handle events from a subsurface.
91+
if !surface
92+
.data::<SurfaceData>()
93+
.is_some_and(|data| data.parent_surface().is_none())
94+
{
95+
return;
96+
}
97+
98+
let window_id = crate::make_wid(&surface);
99+
100+
pointer_gesture_data.window_id = Some(window_id);
101+
pointer_gesture_data.previous_pinch = 1.;
102+
103+
(window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.)
104+
},
105+
Event::Update { dx, dy, scale: pinch, rotation, .. } => {
106+
let window_id = match pointer_gesture_data.window_id {
107+
Some(window_id) => window_id,
108+
_ => return,
109+
};
110+
111+
let scale_factor = match state.windows.get_mut().get_mut(&window_id) {
112+
Some(window) => window.lock().unwrap().scale_factor(),
113+
None => return,
114+
};
115+
116+
let pan_delta =
117+
LogicalPosition::new(dx as f32, dy as f32).to_physical(scale_factor);
118+
119+
let pinch_delta = pinch - pointer_gesture_data.previous_pinch;
120+
pointer_gesture_data.previous_pinch = pinch;
121+
122+
// Wayland provides rotation in degrees cw, opposite of winit's degrees ccw.
123+
let rotation_delta = -rotation as f32;
124+
(window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta)
125+
},
126+
Event::End { cancelled, .. } => {
127+
let window_id = match pointer_gesture_data.window_id {
128+
Some(window_id) => window_id,
129+
_ => return,
130+
};
131+
132+
// Reset the state.
133+
*pointer_gesture_data = Default::default();
134+
135+
let phase = if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled };
136+
(window_id, phase, PhysicalPosition::new(0., 0.), 0., 0.)
137+
},
138+
_ => unreachable!("Unknown event {event:?}"),
139+
};
140+
141+
// The chance of only one of these events being necessary is extremely small,
142+
// so it is easier to just send all three
143+
state.events_sink.push_window_event(
144+
WindowEvent::PanGesture { device_id: None, delta: pan_delta, phase },
145+
window_id,
146+
);
147+
state.events_sink.push_window_event(
148+
WindowEvent::PinchGesture { device_id: None, delta: pinch_delta, phase },
149+
window_id,
150+
);
151+
state.events_sink.push_window_event(
152+
WindowEvent::RotationGesture { device_id: None, delta: rotation_delta, phase },
153+
window_id,
154+
);
155+
}
156+
}
157+
158+
delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState);
159+
delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState);

winit-wayland/src/state.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use winit_core::error::OsError;
2525
use crate::event_loop::sink::EventSink;
2626
use crate::output::MonitorHandle;
2727
use crate::seat::{
28-
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
29-
WinitPointerDataExt, WinitSeatState,
28+
PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState,
29+
WinitPointerData, WinitPointerDataExt, WinitSeatState,
3030
};
3131
use crate::types::kwin_blur::KWinBlurManager;
3232
use crate::types::wp_fractional_scaling::FractionalScalingManager;
@@ -103,6 +103,9 @@ pub struct WinitState {
103103
/// Pointer constraints to handle pointer locking and confining.
104104
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
105105

106+
/// Pointer gestures to handle pinch, rotate, and pan
107+
pub pointer_gestures: Option<PointerGesturesState>,
108+
106109
/// Viewporter state on the given window.
107110
pub viewporter_state: Option<ViewporterState>,
108111

@@ -195,6 +198,7 @@ impl WinitState {
195198
.map(Arc::new)
196199
.ok(),
197200
pointer_surfaces: Default::default(),
201+
pointer_gestures: PointerGesturesState::new(globals, queue_handle).ok(),
198202

199203
monitors: Arc::new(Mutex::new(monitors)),
200204
events_sink: EventSink::new(),

winit/src/changelog/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ changelog entry.
8282
- `ActivationToken::as_raw` to get a ref to raw token.
8383
- Each platform now has corresponding `WindowAttributes` struct instead of trait extension.
8484
- On Wayland, added implementation for `Window::set_window_icon`
85+
- On Wayland, added `PanGesture`, `PinchGesture`, and `RotationGesture`
8586
- Add `Window::request_ime_update` to atomically apply set of IME changes.
8687
- Add `Ime::DeleteSurrounding` to let the input method delete text.
8788
- Add more `ImePurpose` values.

0 commit comments

Comments
 (0)