diff --git a/Cargo.toml b/Cargo.toml index b52a436dce..8d1645b2da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.29.15" +version = "0.30.0" authors = [ "The winit contributors", "Pierre Krieger ", @@ -14,6 +14,7 @@ rust-version.workspace = true repository.workspace = true license.workspace = true edition.workspace = true +exclude = ["/.cargo"] [package.metadata.docs.rs] features = [ @@ -102,9 +103,8 @@ softbuffer = { version = "0.4.0", default-features = false, features = [ ] } [target.'cfg(target_os = "android")'.dependencies] -android-activity = "0.5.0" -ndk = { version = "0.8.0", default-features = false } -ndk-sys = "0.5.0" +android-activity = "0.6.0" +ndk = { version = "0.9.0", default-features = false } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.9.3" @@ -137,7 +137,6 @@ features = [ "NSApplication", "NSBitmapImageRep", "NSButton", - "NSColor", "NSControl", "NSCursor", "NSDragging", diff --git a/README.md b/README.md index 01376de776..f636bfef38 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.29.15" +winit = "0.30.0" ``` ## [Documentation](https://docs.rs/winit) diff --git a/examples/window.rs b/examples/window.rs index 83a31e1346..3a4bd61519 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -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)?; @@ -428,6 +429,11 @@ impl ApplicationHandler 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"); }, @@ -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, #[cfg(macos_platform)] option_as_alt: OptionAsAlt, @@ -547,6 +555,7 @@ impl WindowState { modifiers: Default::default(), occluded: Default::default(), rotated: Default::default(), + panned: Default::default(), zoom: Default::default(), }; diff --git a/src/changelog/mod.rs b/src/changelog/mod.rs index ee86b84a11..a05ea9ccd2 100644 --- a/src/changelog/mod.rs +++ b/src/changelog/mod.rs @@ -5,7 +5,10 @@ // Put the current entry at the top of this page, for discoverability. // See `.cargo/config.toml` for details about `unreleased_changelogs`. #![cfg_attr(unreleased_changelogs, doc = include_str!("unreleased.md"))] -#![cfg_attr(not(unreleased_changelogs), doc = include_str!("v0.29.md"))] +#![cfg_attr(not(unreleased_changelogs), doc = include_str!("v0.30.md"))] + +#[doc = include_str!("v0.30.md")] +pub mod v0_30 {} #[doc = include_str!("v0.29.md")] pub mod v0_29 {} diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 28484b354c..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,222 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- Add `OwnedDisplayHandle` type for allowing safe display handle usage outside of - trivial cases. -- Add `ApplicationHandler` trait which mimics `Event`. -- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a - `CursorIcon` or `CustomCursor`. -- Add `Sync` implementation for `EventLoopProxy`. -- Add `Window::default_attributes` to get default `WindowAttributes`. -- Add `EventLoop::builder` to get `EventLoopBuilder` without export. -- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data. -- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. -- Add `CustomCursorExtWebSys::from_animation` to allow creating animated - cursors from other `CustomCursor`s. -- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources. -- Add `ActiveEventLoop::create_window` and `EventLoop::create_window`. -- 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 macOS, add services menu. -- On Windows, add `with_title_text_color`, and `with_corner_preference` on - `WindowAttributesExtWindows`. -- On Windows, implement resize increments. -- On Windows, add `AnyThread` API to access window handle off the main thread. - -### Changed - -- Bump MSRV from `1.65` to `1.70`. -- On Wayland, bump `sctk-adwaita` to `0.9.0`, which changed system library - crates. This change is a **cascading breaking change**, you must do breaking - change as well, even if you don't expose winit. -- Rename `TouchpadMagnify` to `PinchGesture`. -- Rename `SmartMagnify` to `DoubleTapGesture`. -- Rename `TouchpadRotate` to `RotationGesture`. -- Rename `EventLoopWindowTarget` to `ActiveEventLoop`. -- Rename `platform::x11::XWindowType` to `platform::x11::WindowType`. -- Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold - static data. -- Make `Debug` formatting of `WindowId` more concise. -- Move `dpi` types to its own crate, and re-export it from the root crate. -- Replace `log` with `tracing`, use `log` feature on `tracing` to restore old - behavior. -- `EventLoop::with_user_event` now returns `EventLoopBuilder`. -- On Web, return `HandleError::Unavailable` when a window handle is not available. -- On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`. -- On Web, remove queuing fullscreen request in absence of transient activation. -- On iOS, return `HandleError::Unavailable` when a window handle is not available. -- On macOS, return `HandleError::Unavailable` when a window handle is not available. -- On Windows, remove `WS_CAPTION`, `WS_BORDER`, and `WS_EX_WINDOWEDGE` styles - for child windows without decorations. - -### Deprecated - -- Deprecate `EventLoop::run`, use `EventLoop::run_app`. -- Deprecate `EventLoopExtRunOnDemand::run_on_demand`, use `EventLoop::run_app_on_demand`. -- Deprecate `EventLoopExtPumpEvents::pump_events`, use `EventLoopExtPumpEvents::pump_app_events`. - - The new `app` APIs accept a newly added `ApplicationHandler` instead of - `Fn`. The semantics are mostly the same, given that the capture list of the - closure is your new `State`. Consider the following code: - - ```rust,no_run - use winit::event::Event; - use winit::event_loop::EventLoop; - use winit::window::Window; - - struct MyUserEvent; - - let event_loop = EventLoop::::with_user_event().build().unwrap(); - let window = event_loop.create_window(Window::default_attributes()).unwrap(); - let mut counter = 0; - - let _ = event_loop.run(move |event, event_loop| { - match event { - Event::AboutToWait => { - window.request_redraw(); - counter += 1; - } - Event::WindowEvent { window_id, event } => { - // Handle window event. - } - Event::UserEvent(event) => { - // Handle user event. - } - Event::DeviceEvent { device_id, event } => { - // Handle device event. - } - _ => (), - } - }); - ``` - - To migrate this code, you should move all the captured values into some - newtype `State` and implement `ApplicationHandler` for this type. Finally, - we move particular `match event` arms into methods on `ApplicationHandler`, - for example: - - ```rust,no_run - use winit::application::ApplicationHandler; - use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; - use winit::event_loop::{EventLoop, ActiveEventLoop}; - use winit::window::{Window, WindowId}; - - struct MyUserEvent; - - struct State { - window: Window, - counter: i32, - } - - impl ApplicationHandler for State { - fn user_event(&mut self, event_loop: &ActiveEventLoop, user_event: MyUserEvent) { - // Handle user event. - } - - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - // Your application got resumed. - } - - fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { - // Handle window event. - } - - fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { - // Handle device event. - } - - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - self.window.request_redraw(); - self.counter += 1; - } - } - - let event_loop = EventLoop::::with_user_event().build().unwrap(); - #[allow(deprecated)] - let window = event_loop.create_window(Window::default_attributes()).unwrap(); - let mut state = State { window, counter: 0 }; - - let _ = event_loop.run_app(&mut state); - ``` - - Please submit your feedback after migrating in [this issue](https://github.com/rust-windowing/winit/issues/3626). - -- Deprecate `Window::set_cursor_icon`, use `Window::set_cursor`. - -### Removed - -- Remove `Window::new`, use `ActiveEventLoop::create_window` instead. - - You now have to create your windows inside the actively running event loop - (usually the `new_events(cause: StartCause::Init)` or `resumed()` events), - and can no longer do it before the application has properly launched. - This change is done to fix many long-standing issues on iOS and macOS, and - will improve things on Wayland once fully implemented. - - To ease migration, we provide the deprecated `EventLoop::create_window` that - will allow you to bypass this restriction in this release. - - Using the migration example from above, you can change your code as follows: - - ```rust,no_run - use winit::application::ApplicationHandler; - use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; - use winit::event_loop::{EventLoop, ActiveEventLoop}; - use winit::window::{Window, WindowId}; - - #[derive(Default)] - struct State { - // Use an `Option` to allow the window to not be available until the - // application is properly running. - window: Option, - counter: i32, - } - - impl ApplicationHandler for State { - // This is a common indicator that you can create a window. - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); - } - fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { - // `unwrap` is fine, the window will always be available when - // receiving a window event. - let window = self.window.as_ref().unwrap(); - // Handle window event. - } - fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { - // Handle window event. - } - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - if let Some(window) = self.window.as_ref() { - window.request_redraw(); - self.counter += 1; - } - } - } - - let event_loop = EventLoop::new().unwrap(); - let mut state = State::default(); - let _ = event_loop.run_app(&mut state); - ``` - -- Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`. -- Remove `WindowBuilder` in favor of `WindowAttributes`. -- Remove Generic parameter `T` from `ActiveEventLoop`. -- Remove `EventLoopBuilder::with_user_event`, use `EventLoop::with_user_event`. -- Remove Redundant `EventLoopError::AlreadyRunning`. -- Remove `WindowAttributes::fullscreen` and expose as field directly. -- On X11, remove `platform::x11::XNotSupported` export. - -### Fixed - -- On Web, fix setting cursor icon overriding cursor visibility. -- On Windows, fix cursor not confined to center of window when grabbed and hidden. -- On macOS, fix sequence of mouse events being out of order when dragging on the trackpad. -- On Wayland, fix decoration glitch on close with some compositors. -- On Android, fix a regression introduced in #2748 to allow volume key events to be received again. -- On Windows, don't return a valid window handle outside of the GUI thread. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md new file mode 100644 index 0000000000..e82a162a55 --- /dev/null +++ b/src/changelog/v0.30.md @@ -0,0 +1,224 @@ +## 0.30.0 + +### Added + +- Add `OwnedDisplayHandle` type for allowing safe display handle usage outside of + trivial cases. +- Add `ApplicationHandler` trait which mimics `Event`. +- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a + `CursorIcon` or `CustomCursor`. +- Add `Sync` implementation for `EventLoopProxy`. +- Add `Window::default_attributes` to get default `WindowAttributes`. +- Add `EventLoop::builder` to get `EventLoopBuilder` without export. +- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data. +- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. +- Add `CustomCursorExtWebSys::from_animation` to allow creating animated + cursors from other `CustomCursor`s. +- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources. +- Add `ActiveEventLoop::create_window` and `EventLoop::create_window`. +- 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`, `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`. +- On Windows, implement resize increments. +- On Windows, add `AnyThread` API to access window handle off the main thread. + +### Changed + +- Bump MSRV from `1.65` to `1.70`. +- On Wayland, bump `sctk-adwaita` to `0.9.0`, which changed system library + crates. This change is a **cascading breaking change**, you must do breaking + change as well, even if you don't expose winit. +- Rename `TouchpadMagnify` to `PinchGesture`. +- Rename `SmartMagnify` to `DoubleTapGesture`. +- Rename `TouchpadRotate` to `RotationGesture`. +- Rename `EventLoopWindowTarget` to `ActiveEventLoop`. +- Rename `platform::x11::XWindowType` to `platform::x11::WindowType`. +- Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold + static data. +- Make `Debug` formatting of `WindowId` more concise. +- Move `dpi` types to its own crate, and re-export it from the root crate. +- Replace `log` with `tracing`, use `log` feature on `tracing` to restore old + behavior. +- `EventLoop::with_user_event` now returns `EventLoopBuilder`. +- On Web, return `HandleError::Unavailable` when a window handle is not available. +- On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`. +- On Web, remove queuing fullscreen request in absence of transient activation. +- On iOS, return `HandleError::Unavailable` when a window handle is not available. +- On macOS, return `HandleError::Unavailable` when a window handle is not available. +- On Windows, remove `WS_CAPTION`, `WS_BORDER`, and `WS_EX_WINDOWEDGE` styles + for child windows without decorations. +- On Android, bump `ndk` to `0.9.0` and `android-activity` to `0.6.0`, + and remove unused direct dependency on `ndk-sys`. + +### Deprecated + +- Deprecate `EventLoop::run`, use `EventLoop::run_app`. +- Deprecate `EventLoopExtRunOnDemand::run_on_demand`, use `EventLoop::run_app_on_demand`. +- Deprecate `EventLoopExtPumpEvents::pump_events`, use `EventLoopExtPumpEvents::pump_app_events`. + + The new `app` APIs accept a newly added `ApplicationHandler` instead of + `Fn`. The semantics are mostly the same, given that the capture list of the + closure is your new `State`. Consider the following code: + + ```rust,no_run + use winit::event::Event; + use winit::event_loop::EventLoop; + use winit::window::Window; + + struct MyUserEvent; + + let event_loop = EventLoop::::with_user_event().build().unwrap(); + let window = event_loop.create_window(Window::default_attributes()).unwrap(); + let mut counter = 0; + + let _ = event_loop.run(move |event, event_loop| { + match event { + Event::AboutToWait => { + window.request_redraw(); + counter += 1; + } + Event::WindowEvent { window_id, event } => { + // Handle window event. + } + Event::UserEvent(event) => { + // Handle user event. + } + Event::DeviceEvent { device_id, event } => { + // Handle device event. + } + _ => (), + } + }); + ``` + + To migrate this code, you should move all the captured values into some + newtype `State` and implement `ApplicationHandler` for this type. Finally, + we move particular `match event` arms into methods on `ApplicationHandler`, + for example: + + ```rust,no_run + use winit::application::ApplicationHandler; + use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; + use winit::event_loop::{EventLoop, ActiveEventLoop}; + use winit::window::{Window, WindowId}; + + struct MyUserEvent; + + struct State { + window: Window, + counter: i32, + } + + impl ApplicationHandler for State { + fn user_event(&mut self, event_loop: &ActiveEventLoop, user_event: MyUserEvent) { + // Handle user event. + } + + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + // Your application got resumed. + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { + // Handle window event. + } + + fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { + // Handle device event. + } + + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + self.window.request_redraw(); + self.counter += 1; + } + } + + let event_loop = EventLoop::::with_user_event().build().unwrap(); + #[allow(deprecated)] + let window = event_loop.create_window(Window::default_attributes()).unwrap(); + let mut state = State { window, counter: 0 }; + + let _ = event_loop.run_app(&mut state); + ``` + + Please submit your feedback after migrating in [this issue](https://github.com/rust-windowing/winit/issues/3626). + +- Deprecate `Window::set_cursor_icon`, use `Window::set_cursor`. + +### Removed + +- Remove `Window::new`, use `ActiveEventLoop::create_window` instead. + + You now have to create your windows inside the actively running event loop + (usually the `new_events(cause: StartCause::Init)` or `resumed()` events), + and can no longer do it before the application has properly launched. + This change is done to fix many long-standing issues on iOS and macOS, and + will improve things on Wayland once fully implemented. + + To ease migration, we provide the deprecated `EventLoop::create_window` that + will allow you to bypass this restriction in this release. + + Using the migration example from above, you can change your code as follows: + + ```rust,no_run + use winit::application::ApplicationHandler; + use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; + use winit::event_loop::{EventLoop, ActiveEventLoop}; + use winit::window::{Window, WindowId}; + + #[derive(Default)] + struct State { + // Use an `Option` to allow the window to not be available until the + // application is properly running. + window: Option, + counter: i32, + } + + impl ApplicationHandler for State { + // This is a common indicator that you can create a window. + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); + } + fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { + // `unwrap` is fine, the window will always be available when + // receiving a window event. + let window = self.window.as_ref().unwrap(); + // Handle window event. + } + fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { + // Handle window event. + } + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + if let Some(window) = self.window.as_ref() { + window.request_redraw(); + self.counter += 1; + } + } + } + + let event_loop = EventLoop::new().unwrap(); + let mut state = State::default(); + let _ = event_loop.run_app(&mut state); + ``` + +- Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`. +- Remove `WindowBuilder` in favor of `WindowAttributes`. +- Remove Generic parameter `T` from `ActiveEventLoop`. +- Remove `EventLoopBuilder::with_user_event`, use `EventLoop::with_user_event`. +- Remove Redundant `EventLoopError::AlreadyRunning`. +- Remove `WindowAttributes::fullscreen` and expose as field directly. +- On X11, remove `platform::x11::XNotSupported` export. + +### Fixed + +- On Web, fix setting cursor icon overriding cursor visibility. +- On Windows, fix cursor not confined to center of window when grabbed and hidden. +- On macOS, fix sequence of mouse events being out of order when dragging on the trackpad. +- On Wayland, fix decoration glitch on close with some compositors. +- On Android, fix a regression introduced in #2748 to allow volume key events to be received again. +- On Windows, don't return a valid window handle outside of the GUI thread. +- On macOS, don't set the background color when initializing a window with transparency. diff --git a/src/event.rs b/src/event.rs index c8ce291003..5cd3877a26 100644 --- a/src/event.rs +++ b/src/event.rs @@ -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, + phase: TouchPhase, + }, + /// Double tap gesture. /// /// On a Mac, smart magnification is triggered by a double tap with two fingers @@ -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. /// @@ -993,6 +1011,7 @@ impl PartialEq for InnerSizeWriter { #[cfg(test)] mod tests { + use crate::dpi::PhysicalPosition; use crate::event; use std::collections::{BTreeSet, HashSet}; @@ -1055,6 +1074,11 @@ mod tests { delta: 0.0, phase: event::TouchPhase::Started, }); + with_window_event(PanGesture { + device_id: did, + delta: PhysicalPosition::::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 { diff --git a/src/platform/android.rs b/src/platform/android.rs index 5d13364b6f..5255a31870 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -19,6 +19,7 @@ //! //! | winit | ndk-glue | //! | :---: | :--------------------------: | +//! | 0.30 | `android-activity = "0.6"` | //! | 0.29 | `android-activity = "0.5"` | //! | 0.28 | `android-activity = "0.4"` | //! | 0.27 | `ndk-glue = "0.7"` | @@ -61,7 +62,7 @@ //! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building //! with `cargo apk`, then the minimal changes would be: //! 1. Remove `ndk-glue` from your `Cargo.toml` -//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.15", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.0", //! features = [ "android-native-activity" ] }` //! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 500cf3db19..daba5827ba 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -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. @@ -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)); diff --git a/src/platform_impl/ios/uikit/gesture_recognizer.rs b/src/platform_impl/ios/uikit/gesture_recognizer.rs index 9092471a3c..ab5e7a329d 100644 --- a/src/platform_impl/ios/uikit/gesture_recognizer.rs +++ b/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; @@ -17,6 +21,14 @@ 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 delegate; + #[method(setDelegate:)] + pub fn setDelegate(&self, delegate: &ProtocolObject); + + #[method_id(delegate)] + pub fn delegate(&self) -> Id>; } ); @@ -24,7 +36,7 @@ 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); @@ -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; @@ -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; @@ -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; @@ -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"; + } +); diff --git a/src/platform_impl/ios/uikit/mod.rs b/src/platform_impl/ios/uikit/mod.rs index 88d5a84656..c415cac018 100644 --- a/src/platform_impl/ios/uikit/mod.rs +++ b/src/platform_impl/ios/uikit/mod.rs @@ -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}; diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 8531a60d14..39316dd1a0 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -1,18 +1,19 @@ #![allow(clippy::unnecessary_cast)] -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use objc2::rc::Id; -use objc2::runtime::AnyClass; +use objc2::runtime::{AnyClass, NSObjectProtocol, ProtocolObject}; use objc2::{ declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, }; -use objc2_foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet}; +use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet}; use super::app_state::{self, EventWrapper}; use super::uikit::{ - UIEvent, UIForceTouchCapability, UIGestureRecognizerState, UIPinchGestureRecognizer, - UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase, - UITouchType, UITraitCollection, UIView, + UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate, + UIGestureRecognizerState, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIResponder, + UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase, UITouchType, + UITraitCollection, UIView, }; use super::window::WinitUIWindow; use crate::dpi::PhysicalPosition; @@ -24,6 +25,12 @@ pub struct WinitViewState { pinch_gesture_recognizer: RefCell>>, doubletap_gesture_recognizer: RefCell>>, rotation_gesture_recognizer: RefCell>>, + pan_gesture_recognizer: RefCell>>, + + // for iOS delta references the start of the Gesture + rotation_last_delta: Cell, + pinch_last_delta: Cell, + pan_last_delta: Cell, } declare_class!( @@ -165,12 +172,23 @@ declare_class!( fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) { let window = self.window().unwrap(); - let phase = match recognizer.state() { - UIGestureRecognizerState::Began => TouchPhase::Started, - UIGestureRecognizerState::Changed => TouchPhase::Moved, - UIGestureRecognizerState::Ended => TouchPhase::Ended, + let (phase, delta) = match recognizer.state() { + UIGestureRecognizerState::Began => { + self.ivars().pinch_last_delta.set(recognizer.scale()); + (TouchPhase::Started, 0.0) + } + UIGestureRecognizerState::Changed => { + let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale()); + (TouchPhase::Moved, recognizer.scale() - last_scale) + } + UIGestureRecognizerState::Ended => { + let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0); + (TouchPhase::Moved, recognizer.scale() - last_scale) + } UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { - TouchPhase::Cancelled + self.ivars().rotation_last_delta.set(0.0); + // Pass -delta so that action is reversed + (TouchPhase::Cancelled, -recognizer.scale()) } state => panic!("unexpected recognizer state: {:?}", state), }; @@ -179,7 +197,7 @@ declare_class!( window_id: RootWindowId(window.id()), event: WindowEvent::PinchGesture { device_id: DEVICE_ID, - delta: recognizer.velocity() as _, + delta: delta as f64, phase, }, }); @@ -209,23 +227,88 @@ declare_class!( fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) { let window = self.window().unwrap(); - let phase = match recognizer.state() { - UIGestureRecognizerState::Began => TouchPhase::Started, - UIGestureRecognizerState::Changed => TouchPhase::Moved, - UIGestureRecognizerState::Ended => TouchPhase::Ended, + let (phase, delta) = match recognizer.state() { + UIGestureRecognizerState::Began => { + self.ivars().rotation_last_delta.set(0.0); + + (TouchPhase::Started, 0.0) + } + UIGestureRecognizerState::Changed => { + let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation()); + + (TouchPhase::Moved, recognizer.rotation() - last_rotation) + } + UIGestureRecognizerState::Ended => { + let last_rotation = self.ivars().rotation_last_delta.replace(0.0); + + (TouchPhase::Ended, recognizer.rotation() - last_rotation) + } UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { - TouchPhase::Cancelled + self.ivars().rotation_last_delta.set(0.0); + + // Pass -delta so that action is reversed + (TouchPhase::Cancelled, -recognizer.rotation()) } state => panic!("unexpected recognizer state: {:?}", state), }; - // Flip the velocity to match macOS. - let delta = -recognizer.velocity() as _; + // Make delta negative to match macos, convert to degrees let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RotationGesture { device_id: DEVICE_ID, - delta, + delta: -delta.to_degrees() as _, + phase, + }, + }); + + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_event(mtm, gesture_event); + } + + #[method(panGesture:)] + fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) { + let window = self.window().unwrap(); + + let translation = recognizer.translationInView(self); + + let (phase, dx, dy) = match recognizer.state() { + UIGestureRecognizerState::Began => { + self.ivars().pan_last_delta.set(translation); + + (TouchPhase::Started, 0.0, 0.0) + } + UIGestureRecognizerState::Changed => { + let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation); + + let dx = translation.x - last_pan.x; + let dy = translation.y - last_pan.y; + + (TouchPhase::Moved, dx, dy) + } + UIGestureRecognizerState::Ended => { + let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); + + let dx = translation.x - last_pan.x; + let dy = translation.y - last_pan.y; + + (TouchPhase::Ended, dx, dy) + } + UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { + let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); + + // Pass -delta so that action is reversed + (TouchPhase::Cancelled, -last_pan.x, -last_pan.y) + } + state => panic!("unexpected recognizer state: {:?}", state), + }; + + + let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.id()), + event: WindowEvent::PanGesture { + device_id: DEVICE_ID, + delta: PhysicalPosition::new(dx as _, dy as _), phase, }, }); @@ -234,6 +317,15 @@ declare_class!( app_state::handle_nonuser_event(mtm, gesture_event); } } + + unsafe impl NSObjectProtocol for WinitView {} + + unsafe impl UIGestureRecognizerDelegate for WinitView { + #[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] + fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool { + true + } + } ); extern_methods!( @@ -263,6 +355,11 @@ impl WinitView { pinch_gesture_recognizer: RefCell::new(None), doubletap_gesture_recognizer: RefCell::new(None), rotation_gesture_recognizer: RefCell::new(None), + pan_gesture_recognizer: RefCell::new(None), + + rotation_last_delta: Cell::new(0.0), + pinch_last_delta: Cell::new(0.0), + pan_last_delta: Cell::new(CGPoint { x: 0.0, y: 0.0 }), }); let this: Id = unsafe { msg_send_id![super(this), initWithFrame: frame] }; @@ -281,6 +378,7 @@ impl WinitView { let pinch: Id = unsafe { msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)] }; + pinch.setDelegate(ProtocolObject::from_ref(self)); self.addGestureRecognizer(&pinch); self.ivars().pinch_gesture_recognizer.replace(Some(pinch)); } @@ -289,12 +387,35 @@ impl WinitView { } } + pub(crate) fn recognize_pan_gesture( + &self, + should_recognize: bool, + minimum_number_of_touches: u8, + maximum_number_of_touches: u8, + ) { + if should_recognize { + if self.ivars().pan_gesture_recognizer.borrow().is_none() { + let pan: Id = unsafe { + msg_send_id![UIPanGestureRecognizer::alloc(), initWithTarget: self, action: sel!(panGesture:)] + }; + pan.setDelegate(ProtocolObject::from_ref(self)); + pan.setMinimumNumberOfTouches(minimum_number_of_touches as _); + pan.setMaximumNumberOfTouches(maximum_number_of_touches as _); + self.addGestureRecognizer(&pan); + self.ivars().pan_gesture_recognizer.replace(Some(pan)); + } + } else if let Some(recognizer) = self.ivars().pan_gesture_recognizer.take() { + self.removeGestureRecognizer(&recognizer); + } + } + pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) { if should_recognize { if self.ivars().doubletap_gesture_recognizer.borrow().is_none() { let tap: Id = unsafe { msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)] }; + tap.setDelegate(ProtocolObject::from_ref(self)); tap.setNumberOfTapsRequired(2); tap.setNumberOfTouchesRequired(1); self.addGestureRecognizer(&tap); @@ -311,6 +432,7 @@ impl WinitView { let rotation: Id = unsafe { msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)] }; + rotation.setDelegate(ProtocolObject::from_ref(self)); self.addGestureRecognizer(&rotation); self.ivars().rotation_gesture_recognizer.replace(Some(rotation)); } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 1d9310daaa..4a4f975ce6 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -619,6 +619,19 @@ impl Inner { self.view.recognize_pinch_gesture(should_recognize); } + pub fn recognize_pan_gesture( + &self, + should_recognize: bool, + minimum_number_of_touches: u8, + maximum_number_of_touches: u8, + ) { + self.view.recognize_pan_gesture( + should_recognize, + minimum_number_of_touches, + maximum_number_of_touches, + ); + } + pub fn recognize_doubletap_gesture(&self, should_recognize: bool) { self.view.recognize_doubletap_gesture(should_recognize); } diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 103454ce05..08e4504a83 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -72,7 +72,7 @@ pub struct Window { /// Source to wake-up the event-loop for window requests. event_loop_awakener: calloop::ping::Ping, - /// The event sink to deliver sythetic events. + /// The event sink to deliver synthetic events. window_events_sink: Arc>, } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 19cbba7f30..4eb99cac5b 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1778,7 +1778,7 @@ impl EventProcessor { None => return, }; - // Send the keys using the sythetic state to not alter the main state. + // Send the keys using the synthetic state to not alter the main state. let mut xkb_state = match XkbState::new_x11(xcb, keymap) { Some(xkb_state) => xkb_state, None => return, diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index e28b92e4af..63ec564730 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -180,7 +180,7 @@ impl XConnection { // Position relative to root window. // With rare exceptions, this is the position of a nested window. Cases where the window - // isn't nested are outlined in the comments throghout this function, but in addition to + // isn't nested are outlined in the comments throughout this function, but in addition to // that, fullscreen windows often aren't nested. let (inner_y_rel_root, child) = { let coords = self diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 6b4f8369c0..d3c5b362ea 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -53,7 +53,7 @@ impl PanicInfo { result } - /// Overwrites the curret state if the current state is not panicking + /// Overwrites the current state if the current state is not panicking pub fn set_panic(&self, p: Box) { if !self.is_panicking() { self.inner.set(Some(p)); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 7e48df075f..b9e51a01ab 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -12,7 +12,7 @@ use objc2::{ }; use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication, - NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, + NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, @@ -605,7 +605,6 @@ fn new_window(attrs: &WindowAttributes, mtm: MainThreadMarker) -> Option