Skip to content

Commit

Permalink
Add UIGestureRecognizerDelegate and PanGestureRecogniser (#3597)
Browse files Browse the repository at this point in the history
- Allow all gestures simultaneously recognized.
- Add PanGestureRecogniser with min/max number of touches.
- Fix sending delta values relative to Update instead to match macOS.
- Fix rotation gesture units from iOS to be in degrees instead of radians.

Co-authored-by: Mads Marquart <mads@marquart.dk>
  • Loading branch information
jpedrick and madsmtm committed Apr 27, 2024
1 parent 94664ff commit fd47798
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 31 deletions.
9 changes: 9 additions & 0 deletions examples/window.rs
Expand Up @@ -159,6 +159,7 @@ impl Application {
window.recognize_doubletap_gesture(true);
window.recognize_pinch_gesture(true);
window.recognize_rotation_gesture(true);
window.recognize_pan_gesture(true, 2, 2);
}

let window_state = WindowState::new(self, window)?;
Expand Down Expand Up @@ -428,6 +429,11 @@ impl ApplicationHandler<UserEvent> for Application {
info!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
}
},
WindowEvent::PanGesture { delta, phase, .. } => {
window.panned.x += delta.x;
window.panned.y += delta.y;
info!("Panned ({delta:?})) (now: {:?}), {phase:?}", window.panned);
},
WindowEvent::DoubleTapGesture { .. } => {
info!("Smart zoom");
},
Expand Down Expand Up @@ -502,6 +508,8 @@ struct WindowState {
zoom: f64,
/// The amount of rotation of the window.
rotated: f32,
/// The amount of pan of the window.
panned: PhysicalPosition<f32>,

#[cfg(macos_platform)]
option_as_alt: OptionAsAlt,
Expand Down Expand Up @@ -547,6 +555,7 @@ impl WindowState {
modifiers: Default::default(),
occluded: Default::default(),
rotated: Default::default(),
panned: Default::default(),
zoom: Default::default(),
};

Expand Down
3 changes: 2 additions & 1 deletion src/changelog/unreleased.md
Expand Up @@ -59,7 +59,8 @@ changelog entry.
- Add `CustomCursor` which could be set via `Window::set_cursor`, implemented on
Windows, macOS, X11, Wayland, and Web.
- On Web, add to toggle calling `Event.preventDefault()` on `Window`.
- On iOS, add `PinchGesture`, `DoubleTapGesture`, and `RotationGesture`
- On iOS, add `PinchGesture`, `DoubleTapGesture`, `PanGesture` and `RotationGesture`.
- on iOS, use `UIGestureRecognizerDelegate` for fine grained control of gesture recognizers.
- On macOS, add services menu.
- On Windows, add `with_title_text_color`, and `with_corner_preference` on
`WindowAttributesExtWindows`.
Expand Down
26 changes: 25 additions & 1 deletion src/event.rs
Expand Up @@ -293,6 +293,19 @@ pub enum WindowEvent {
phase: TouchPhase,
},

/// N-finger pan gesture
///
/// ## Platform-specific
///
/// - Only available on **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
PanGesture {
device_id: DeviceId,
/// Change in pixels of pan gesture from last update.
delta: PhysicalPosition<f32>,
phase: TouchPhase,
},

/// Double tap gesture.
///
/// On a Mac, smart magnification is triggered by a double tap with two fingers
Expand Down Expand Up @@ -322,7 +335,12 @@ pub enum WindowEvent {
///
/// - Only available on **macOS** and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture { device_id: DeviceId, delta: f32, phase: TouchPhase },
RotationGesture {
device_id: DeviceId,
/// change in rotation in degrees
delta: f32,
phase: TouchPhase,
},

/// Touchpad pressure event.
///
Expand Down Expand Up @@ -993,6 +1011,7 @@ impl PartialEq for InnerSizeWriter {

#[cfg(test)]
mod tests {
use crate::dpi::PhysicalPosition;
use crate::event;
use std::collections::{BTreeSet, HashSet};

Expand Down Expand Up @@ -1055,6 +1074,11 @@ mod tests {
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: did,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: did, pressure: 0.0, stage: 0 });
with_window_event(AxisMotion { device_id: did, axis: 0, value: 0.0 });
with_window_event(Touch(event::Touch {
Expand Down
31 changes: 31 additions & 0 deletions src/platform/ios.rs
Expand Up @@ -155,6 +155,21 @@ pub trait WindowExtIOS {
/// The default is to not recognize gestures.
fn recognize_pinch_gesture(&self, should_recognize: bool);

/// Sets whether the [`Window`] should recognize pan gestures.
///
/// The default is to not recognize gestures.
/// Installs [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) onto view
///
/// Set the minimum number of touches required: [`minimumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-minimumnumberoftouches)
///
/// Set the maximum number of touches recognized: [`maximumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-maximumnumberoftouches)
fn recognize_pan_gesture(
&self,
should_recognize: bool,
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
);

/// Sets whether the [`Window`] should recognize double tap gestures.
///
/// The default is to not recognize gestures.
Expand Down Expand Up @@ -204,6 +219,22 @@ impl WindowExtIOS for Window {
self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}

#[inline]
fn recognize_pan_gesture(
&self,
should_recognize: bool,
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
) {
self.window.maybe_queue_on_main(move |w| {
w.recognize_pan_gesture(
should_recognize,
minimum_number_of_touches,
maximum_number_of_touches,
)
});
}

#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
Expand Down
71 changes: 64 additions & 7 deletions src/platform_impl/ios/uikit/gesture_recognizer.rs
@@ -1,9 +1,13 @@
use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, extern_methods, mutability, ClassType};
use objc2_foundation::{CGFloat, NSInteger, NSObject, NSUInteger};
use objc2::rc::Id;
use objc2::runtime::ProtocolObject;
use objc2::{extern_class, extern_methods, extern_protocol, mutability, ClassType, ProtocolType};
use objc2_foundation::{CGFloat, CGPoint, NSInteger, NSObject, NSObjectProtocol, NSUInteger};

use super::UIView;

// https://developer.apple.com/documentation/uikit/uigesturerecognizer
extern_class!(
/// [`UIGestureRecognizer`](https://developer.apple.com/documentation/uikit/uigesturerecognizer)
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIGestureRecognizer;

Expand All @@ -17,14 +21,22 @@ extern_methods!(
unsafe impl UIGestureRecognizer {
#[method(state)]
pub fn state(&self) -> UIGestureRecognizerState;

/// [`delegate`](https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624207-delegate?language=objc)
/// @property(nullable, nonatomic, weak) id<UIGestureRecognizerDelegate> delegate;
#[method(setDelegate:)]
pub fn setDelegate(&self, delegate: &ProtocolObject<dyn UIGestureRecognizerDelegate>);

#[method_id(delegate)]
pub fn delegate(&self) -> Id<ProtocolObject<dyn UIGestureRecognizerDelegate>>;
}
);

unsafe impl Encode for UIGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}

// https://developer.apple.com/documentation/uikit/uigesturerecognizer/state
// [`UIGestureRecognizerState`](https://developer.apple.com/documentation/uikit/uigesturerecognizer/state)
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIGestureRecognizerState(NSInteger);
Expand All @@ -43,7 +55,7 @@ impl UIGestureRecognizerState {
pub const Failed: Self = Self(5);
}

// https://developer.apple.com/documentation/uikit/uipinchgesturerecognizer
// [`UIPinchGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipinchgesturerecognizer)
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIPinchGestureRecognizer;
Expand All @@ -68,8 +80,8 @@ unsafe impl Encode for UIPinchGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}

// https://developer.apple.com/documentation/uikit/uirotationgesturerecognizer
extern_class!(
/// [`UIRotationGestureRecognizer`](https://developer.apple.com/documentation/uikit/uirotationgesturerecognizer)
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIRotationGestureRecognizer;

Expand All @@ -93,8 +105,8 @@ unsafe impl Encode for UIRotationGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}

// https://developer.apple.com/documentation/uikit/uitapgesturerecognizer
extern_class!(
/// [`UITapGestureRecognizer`](https://developer.apple.com/documentation/uikit/uitapgesturerecognizer)
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITapGestureRecognizer;

Expand All @@ -117,3 +129,48 @@ extern_methods!(
unsafe impl Encode for UITapGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}

extern_class!(
/// [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer)
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIPanGestureRecognizer;

unsafe impl ClassType for UIPanGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);

extern_methods!(
unsafe impl UIPanGestureRecognizer {
#[method(translationInView:)]
pub fn translationInView(&self, view: &UIView) -> CGPoint;

#[method(setTranslation:inView:)]
pub fn setTranslationInView(&self, translation: CGPoint, view: &UIView);

#[method(velocityInView:)]
pub fn velocityInView(&self, view: &UIView) -> CGPoint;

#[method(setMinimumNumberOfTouches:)]
pub fn setMinimumNumberOfTouches(&self, minimum_number_of_touches: NSUInteger);

#[method(minimumNumberOfTouches)]
pub fn minimumNumberOfTouches(&self) -> NSUInteger;

#[method(setMaximumNumberOfTouches:)]
pub fn setMaximumNumberOfTouches(&self, maximum_number_of_touches: NSUInteger);

#[method(maximumNumberOfTouches)]
pub fn maximumNumberOfTouches(&self) -> NSUInteger;
}
);

extern_protocol!(
/// (@protocol UIGestureRecognizerDelegate)[https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate?language=objc]
pub(crate) unsafe trait UIGestureRecognizerDelegate: NSObjectProtocol {}

unsafe impl ProtocolType for dyn UIGestureRecognizerDelegate {
const NAME: &'static str = "UIGestureRecognizerDelegate";
}
);
5 changes: 3 additions & 2 deletions src/platform_impl/ios/uikit/mod.rs
Expand Up @@ -27,8 +27,9 @@ pub(crate) use self::device::{UIDevice, UIUserInterfaceIdiom};
pub(crate) use self::event::UIEvent;
pub(crate) use self::geometry::UIRectEdge;
pub(crate) use self::gesture_recognizer::{
UIGestureRecognizer, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIRotationGestureRecognizer, UITapGestureRecognizer,
UIGestureRecognizer, UIGestureRecognizerDelegate, UIGestureRecognizerState,
UIPanGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer,
UITapGestureRecognizer,
};
pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
Expand Down

0 comments on commit fd47798

Please sign in to comment.