wayland: support for pinch, rotation, and pan gestures
Co-Authored-By: linkmauve <linkmauve@linkmauve.fr> Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
This commit is contained in:
parent
a68f1a664b
commit
3be30affe4
6 changed files with 187 additions and 5 deletions
|
|
@ -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<DeviceId>,
|
||||
|
|
@ -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<DeviceId>,
|
||||
|
|
@ -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<DeviceId>,
|
||||
|
|
|
|||
|
|
@ -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<ZwpRelativePointerV1>,
|
||||
|
||||
/// The pinch pointer gesture bound on the seat.
|
||||
pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>,
|
||||
|
||||
/// The keyboard bound on the seat.
|
||||
keyboard_state: Option<KeyboardState>,
|
||||
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
159
winit-wayland/src/seat/pointer/pointer_gesture.rs
Normal file
159
winit-wayland/src/seat/pointer/pointer_gesture.rs
Normal file
|
|
@ -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<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);
|
||||
|
|
@ -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<Arc<PointerConstraintsState>>,
|
||||
|
||||
/// Pointer gestures to handle pinch, rotate, and pan
|
||||
pub pointer_gestures: Option<PointerGesturesState>,
|
||||
|
||||
/// Viewporter state on the given window.
|
||||
pub viewporter_state: Option<ViewporterState>,
|
||||
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue