diff --git a/crates/bevy_input/src/gestures.rs b/crates/bevy_input/src/gestures.rs new file mode 100644 index 0000000000000..5c7cad689b1ba --- /dev/null +++ b/crates/bevy_input/src/gestures.rs @@ -0,0 +1,73 @@ +//! Gestures functionality, from touchscreens and touchpads. + +use bevy_ecs::event::Event; +use bevy_math::Vec2; +use bevy_reflect::Reflect; + +#[cfg(feature = "serialize")] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; + +/// Two-finger pinch gesture, often used for magnifications. +/// +/// Positive delta values indicate magnification (zooming in) and +/// negative delta values indicate shrinking (zooming out). +/// +/// ## Platform-specific +/// +/// - Only available on **`macOS`** and **`iOS`**. +/// - On **`iOS`**, must be enabled first +#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct PinchGesture(pub f32); + +/// Two-finger rotation gesture. +/// +/// Positive delta values indicate rotation counterclockwise and +/// negative delta values indicate rotation clockwise. +/// +/// ## Platform-specific +/// +/// - Only available on **`macOS`** and **`iOS`**. +/// - On **`iOS`**, must be enabled first +#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct RotationGesture(pub f32); + +/// Double tap gesture. +/// +/// ## Platform-specific +/// +/// - Only available on **`macOS`** and **`iOS`**. +/// - On **`iOS`**, must be enabled first +#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct DoubleTapGesture; + +/// Pan gesture. +/// +/// ## Platform-specific +/// +/// - On **`iOS`**, must be enabled first +#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct PanGesture(pub Vec2); diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 17d392583f27d..3ea2c942f4059 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -16,10 +16,10 @@ mod button_input; /// Common run conditions pub mod common_conditions; pub mod gamepad; +pub mod gestures; pub mod keyboard; pub mod mouse; pub mod touch; -pub mod touchpad; pub use axis::*; pub use button_input::*; @@ -41,10 +41,10 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; +use gestures::*; use keyboard::{keyboard_input_system, KeyCode, KeyboardInput}; use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseWheel}; use touch::{touch_screen_input_system, TouchInput, Touches}; -use touchpad::{TouchpadMagnify, TouchpadRotate}; use gamepad::{ gamepad_axis_event_system, gamepad_button_event_system, gamepad_connection_system, @@ -77,8 +77,10 @@ impl Plugin for InputPlugin { .add_event::() .init_resource::>() .add_systems(PreUpdate, mouse_button_input_system.in_set(InputSystem)) - .add_event::() - .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() // gamepad .add_event::() .add_event::() @@ -114,8 +116,10 @@ impl Plugin for InputPlugin { app.register_type::() .register_type::() .register_type::() - .register_type::() - .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_input/src/touchpad.rs b/crates/bevy_input/src/touchpad.rs deleted file mode 100644 index 46105c9b866d3..0000000000000 --- a/crates/bevy_input/src/touchpad.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! The touchpad input functionality. - -use bevy_ecs::event::Event; -use bevy_reflect::Reflect; - -#[cfg(feature = "serialize")] -use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; - -/// Touchpad magnification event with two-finger pinch gesture. -/// -/// Positive delta values indicate magnification (zooming in) and -/// negative delta values indicate shrinking (zooming out). -/// -/// ## Platform-specific -/// -/// - Only available on **`macOS`**. -#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct TouchpadMagnify(pub f32); - -/// Touchpad rotation event with two-finger rotation gesture. -/// -/// Positive delta values indicate rotation counterclockwise and -/// negative delta values indicate rotation clockwise. -/// -/// ## Platform-specific -/// -/// - Only available on **`macOS`**. -#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct TouchpadRotate(pub f32); diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index fdb8ae5c3d90f..9ce2658f0d0d0 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -281,6 +281,33 @@ pub struct Window { /// [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`]: /// https://docs.rs/wgpu/latest/wgpu/type.SurfaceConfiguration.html#structfield.desired_maximum_frame_latency pub desired_maximum_frame_latency: Option, + /// Sets whether this window recognizes [`PinchGesture`] + /// + /// ## Platform-specific + /// + /// - Only used on iOS. + /// - On macOS, they are recognized by default and can't be disabled. + pub recognize_pinch_gesture: bool, + /// Sets whether this window recognizes [`RotationGesture`] + /// + /// ## Platform-specific + /// + /// - Only used on iOS. + /// - On macOS, they are recognized by default and can't be disabled. + pub recognize_rotation_gesture: bool, + /// Sets whether this window recognizes [`DoubleTapGesture`] + /// + /// ## Platform-specific + /// + /// - Only used on iOS. + /// - On macOS, they are recognized by default and can't be disabled. + pub recognize_doubletap_gesture: bool, + /// Sets whether this window recognizes [`PanGesture`], with a number of fingers between the first value and the last. + /// + /// ## Platform-specific + /// + /// - Only used on iOS. + pub recognize_pan_gesture: Option<(u8, u8)>, } impl Default for Window { @@ -311,6 +338,10 @@ impl Default for Window { visible: true, skip_taskbar: false, desired_maximum_frame_latency: None, + recognize_pinch_gesture: false, + recognize_rotation_gesture: false, + recognize_doubletap_gesture: false, + recognize_pan_gesture: None, } } } diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index df0aab42dec31..bf082aff2c22d 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -7,8 +7,8 @@ use bevy_ecs::prelude::*; use bevy_ecs::system::SystemState; use bevy_ecs::world::FromWorld; use bevy_input::{ + gestures::*, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, - touchpad::{TouchpadMagnify, TouchpadRotate}, }; use bevy_log::{error, trace, warn}; use bevy_math::{ivec2, DVec2, Vec2}; @@ -264,10 +264,19 @@ impl ApplicationHandler for WinitAppRunnerState { }); } WindowEvent::PinchGesture { delta, .. } => { - self.winit_events.send(TouchpadMagnify(delta as f32)); + self.winit_events.send(PinchGesture(delta as f32)); } WindowEvent::RotationGesture { delta, .. } => { - self.winit_events.send(TouchpadRotate(delta)); + self.winit_events.send(RotationGesture(delta)); + } + WindowEvent::DoubleTapGesture { .. } => { + self.winit_events.send(DoubleTapGesture); + } + WindowEvent::PanGesture { delta, .. } => { + self.winit_events.send(PanGesture(Vec2 { + x: delta.x, + y: delta.y, + })); } WindowEvent::MouseWheel { delta, .. } => match delta { event::MouseScrollDelta::LineDelta(x, y) => { @@ -674,10 +683,16 @@ impl WinitAppRunnerState { WinitEvent::MouseWheel(e) => { world.send_event(e); } - WinitEvent::TouchpadMagnify(e) => { + WinitEvent::PinchGesture(e) => { + world.send_event(e); + } + WinitEvent::RotationGesture(e) => { + world.send_event(e); + } + WinitEvent::DoubleTapGesture(e) => { world.send_event(e); } - WinitEvent::TouchpadRotate(e) => { + WinitEvent::PanGesture(e) => { world.send_event(e); } WinitEvent::TouchInput(e) => { diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 57a9de8978adb..fe77e9bbe013a 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -16,6 +16,8 @@ use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event_loop::ActiveEventLoop; use bevy_ecs::query::With; +#[cfg(target_os = "ios")] +use winit::platform::ios::WindowExtIOS; #[cfg(target_arch = "wasm32")] use winit::platform::web::WindowExtWebSys; @@ -97,6 +99,19 @@ pub fn create_windows( style.set_property("height", "100%").unwrap(); } } + + #[cfg(target_os = "ios")] + { + winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); + winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); + winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); + if let Some((min, max)) = window.recognize_pan_gesture { + winit_window.recognize_pan_gesture(true, min, max); + } else { + winit_window.recognize_pan_gesture(false, 0, 0); + } + } + window_created_events.send(WindowCreated { window: entity }); } } @@ -360,6 +375,31 @@ pub(crate) fn changed_windows( winit_window.set_visible(window.visible); } + #[cfg(target_os = "ios")] + { + if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture { + winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); + } + if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture { + winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); + } + if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture { + winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); + } + if window.recognize_pan_gesture != cache.window.recognize_pan_gesture { + match ( + window.recognize_pan_gesture, + cache.window.recognize_pan_gesture, + ) { + (Some(_), Some(_)) => { + warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers"); + } + (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max), + _ => winit_window.recognize_pan_gesture(false, 0, 0), + } + } + } + cache.window = window.clone(); } } diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs index 92d3c775a858b..23988f6ad9a0d 100644 --- a/crates/bevy_winit/src/winit_event.rs +++ b/crates/bevy_winit/src/winit_event.rs @@ -5,8 +5,8 @@ use bevy_ecs::prelude::*; use bevy_input::keyboard::KeyboardInput; use bevy_input::touch::TouchInput; use bevy_input::{ + gestures::*, mouse::{MouseButtonInput, MouseMotion, MouseWheel}, - touchpad::{TouchpadMagnify, TouchpadRotate}, }; use bevy_reflect::Reflect; #[cfg(feature = "serialize")] @@ -55,8 +55,10 @@ pub enum WinitEvent { MouseMotion(MouseMotion), MouseWheel(MouseWheel), - TouchpadMagnify(TouchpadMagnify), - TouchpadRotate(TouchpadRotate), + PinchGesture(PinchGesture), + RotationGesture(RotationGesture), + DoubleTapGesture(DoubleTapGesture), + PanGesture(PanGesture), TouchInput(TouchInput), @@ -168,14 +170,24 @@ impl From for WinitEvent { Self::MouseWheel(e) } } -impl From for WinitEvent { - fn from(e: TouchpadMagnify) -> Self { - Self::TouchpadMagnify(e) +impl From for WinitEvent { + fn from(e: PinchGesture) -> Self { + Self::PinchGesture(e) } } -impl From for WinitEvent { - fn from(e: TouchpadRotate) -> Self { - Self::TouchpadRotate(e) +impl From for WinitEvent { + fn from(e: RotationGesture) -> Self { + Self::RotationGesture(e) + } +} +impl From for WinitEvent { + fn from(e: DoubleTapGesture) -> Self { + Self::DoubleTapGesture(e) + } +} +impl From for WinitEvent { + fn from(e: PanGesture) -> Self { + Self::PanGesture(e) } } impl From for WinitEvent { diff --git a/examples/input/mouse_input_events.rs b/examples/input/mouse_input_events.rs index f1081f41c4436..251072baa9dd1 100644 --- a/examples/input/mouse_input_events.rs +++ b/examples/input/mouse_input_events.rs @@ -2,8 +2,8 @@ use bevy::{ input::{ + gestures::*, mouse::{MouseButtonInput, MouseMotion, MouseWheel}, - touchpad::{TouchpadMagnify, TouchpadRotate}, }, prelude::*, }; @@ -21,8 +21,9 @@ fn print_mouse_events_system( mut mouse_motion_events: EventReader, mut cursor_moved_events: EventReader, mut mouse_wheel_events: EventReader, - mut touchpad_magnify_events: EventReader, - mut touchpad_rotate_events: EventReader, + mut pinch_gesture_events: EventReader, + mut rotation_gesture_events: EventReader, + mut double_tap_gesture_events: EventReader, ) { for event in mouse_button_input_events.read() { info!("{:?}", event); @@ -41,12 +42,17 @@ fn print_mouse_events_system( } // This event will only fire on macOS - for event in touchpad_magnify_events.read() { + for event in pinch_gesture_events.read() { info!("{:?}", event); } // This event will only fire on macOS - for event in touchpad_rotate_events.read() { + for event in rotation_gesture_events.read() { + info!("{:?}", event); + } + + // This event will only fire on macOS + for event in double_tap_gesture_events.read() { info!("{:?}", event); } } diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 45a89a076c8ac..122710733c3c6 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -2,7 +2,7 @@ use bevy::{ color::palettes::basic::*, - input::touch::TouchPhase, + input::{gestures::RotationGesture, touch::TouchPhase}, prelude::*, window::{AppLifecycle, WindowMode}, }; @@ -15,6 +15,9 @@ fn main() { primary_window: Some(Window { resizable: false, mode: WindowMode::BorderlessFullscreen, + // on iOS, gestures must be enabled. + // This doesn't work on Android + recognize_rotation_gesture: true, ..default() }), ..default() @@ -35,6 +38,7 @@ fn touch_camera( mut touches: EventReader, mut camera: Query<&mut Transform, With>, mut last_position: Local>, + mut rotations: EventReader, ) { let window = windows.single(); @@ -55,6 +59,12 @@ fn touch_camera( } *last_position = Some(touch.position); } + // Rotation gestures only work on iOS + for rotation in rotations.read() { + let mut transform = camera.single_mut(); + let forward = transform.forward(); + transform.rotate_axis(forward, rotation.0 / 10.0); + } } /// set up a simple 3D scene