Files
winit/winit-wayland/src/seat/pointer/pointer_gesture.rs
Alan Everett 3be30affe4 wayland: support for pinch, rotation, and pan gestures
Co-Authored-By: linkmauve <linkmauve@linkmauve.fr>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2025-09-07 11:45:35 +09:00

160 lines
5.6 KiB
Rust

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<WinitState>,
) -> Result<Self, BindError> {
let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { pointer_gestures })
}
}
#[derive(Debug, Default)]
pub struct PointerGestureData {
inner: Mutex<PointerGestureDataInner>,
}
#[derive(Debug)]
pub struct PointerGestureDataInner {
window_id: Option<WindowId>,
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<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, PointerGestureData, WinitState> for PointerGesturesState {
fn event(
state: &mut WinitState,
_proxy: &ZwpPointerGesturePinchV1,
event: <ZwpPointerGesturePinchV1 as Proxy>::Event,
data: &PointerGestureData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
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::<SurfaceData>()
.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);