From c4fc5d88f0ba2d86ff6c9a489154e4ca29666e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Lescaudey=20de=20Maneville?= Date: Mon, 20 Jun 2022 20:32:19 +0000 Subject: [PATCH] Fixed bevy_ui touch input (#4099) # Objective `bevy_ui` doesn't support correctly touch inputs because of two problems in the focus system: - It attempts to retrieve touch input with a specific `0` id - It doesn't retrieve touch positions and bases its focus solely on mouse position, absent from mobile devices ## Solution I added a few methods to the `Touches` resource, allowing to check if **any** touch input was pressed, released or cancelled and to retrieve the *position* of the first pressed touch input and adapted the focus system. I added a test button to the *iOS* example and it works correclty on emulator. I did not test on a real touch device as: - Android is not working (https://github.com/bevyengine/bevy/issues/3249) - I don't have an iOS device --- crates/bevy_input/src/touch.rs | 20 +++++++++++ crates/bevy_ui/src/focus.rs | 13 +++---- examples/ios/src/lib.rs | 63 +++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/crates/bevy_input/src/touch.rs b/crates/bevy_input/src/touch.rs index 4ba55c0118967..7333ac2d3eacc 100644 --- a/crates/bevy_input/src/touch.rs +++ b/crates/bevy_input/src/touch.rs @@ -224,6 +224,11 @@ impl Touches { self.pressed.get(&id) } + /// Checks if any touch input was just pressed. + pub fn any_just_pressed(&self) -> bool { + !self.just_pressed.is_empty() + } + /// Returns `true` if the input corresponding to the `id` has just been pressed. pub fn just_pressed(&self, id: u64) -> bool { self.just_pressed.contains_key(&id) @@ -239,6 +244,11 @@ impl Touches { self.just_released.get(&id) } + /// Checks if any touch input was just released. + pub fn any_just_released(&self) -> bool { + !self.just_released.is_empty() + } + /// Returns `true` if the input corresponding to the `id` has just been released. pub fn just_released(&self, id: u64) -> bool { self.just_released.contains_key(&id) @@ -249,6 +259,11 @@ impl Touches { self.just_released.values() } + /// Checks if any touch input was just cancelled. + pub fn any_just_cancelled(&self) -> bool { + !self.just_cancelled.is_empty() + } + /// Returns `true` if the input corresponding to the `id` has just been cancelled. pub fn just_cancelled(&self, id: u64) -> bool { self.just_cancelled.contains_key(&id) @@ -259,6 +274,11 @@ impl Touches { self.just_cancelled.values() } + /// Retrieves the position of the first currently pressed touch, if any + pub fn first_pressed_position(&self) -> Option { + self.pressed.values().next().map(|t| t.position) + } + /// Processes a [`TouchInput`] event by updating the `pressed`, `just_pressed`, /// `just_released`, and `just_cancelled` collections. fn process_touch_event(&mut self, event: &TouchInput) { diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 881376b023280..9e211b2b29bd1 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -71,10 +71,6 @@ pub fn ui_focus_system( Option<&CalculatedClip>, )>, ) { - let cursor_position = windows - .get_primary() - .and_then(|window| window.cursor_position()); - // reset entities that were both clicked and released in the last frame for entity in state.entities_to_reset.drain(..) { if let Ok(mut interaction) = node_query.get_component_mut::(entity) { @@ -83,7 +79,7 @@ pub fn ui_focus_system( } let mouse_released = - mouse_button_input.just_released(MouseButton::Left) || touches_input.just_released(0); + mouse_button_input.just_released(MouseButton::Left) || touches_input.any_just_released(); if mouse_released { for (_entity, _node, _global_transform, interaction, _focus_policy, _clip) in node_query.iter_mut() @@ -97,7 +93,12 @@ pub fn ui_focus_system( } let mouse_clicked = - mouse_button_input.just_pressed(MouseButton::Left) || touches_input.just_pressed(0); + mouse_button_input.just_pressed(MouseButton::Left) || touches_input.any_just_pressed(); + + let cursor_position = windows + .get_primary() + .and_then(|window| window.cursor_position()) + .or_else(|| touches_input.first_pressed_position()); let mut moused_over_z_sorted_nodes = node_query .iter_mut() diff --git a/examples/ios/src/lib.rs b/examples/ios/src/lib.rs index 220f2c3fd0866..aaf4509383ac4 100644 --- a/examples/ios/src/lib.rs +++ b/examples/ios/src/lib.rs @@ -13,13 +13,14 @@ fn main() { .add_startup_system(setup_scene) .add_startup_system(setup_music) .add_system(touch_camera) + .add_system(button_handler) .run(); } fn touch_camera( windows: ResMut, mut touches: EventReader, - mut camera: Query<&mut Transform, With>, + mut camera: Query<&mut Transform, With>, mut last_position: Local>, ) { for touch in touches.iter() { @@ -47,6 +48,7 @@ fn setup_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + asset_server: Res, ) { // plane commands.spawn_bundle(PbrBundle { @@ -86,6 +88,65 @@ fn setup_scene( transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); + + // Test ui + commands.spawn_bundle(Camera2dBundle::default()); + commands + .spawn_bundle(ButtonBundle { + style: Style { + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(50.0), + right: Val::Px(50.0), + top: Val::Auto, + bottom: Val::Px(50.0), + }, + ..default() + }, + ..default() + }) + .with_children(|b| { + b.spawn_bundle(TextBundle { + text: Text { + sections: vec![TextSection { + value: "Test Button".to_string(), + style: TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 30.0, + color: Color::BLACK, + }, + }], + alignment: TextAlignment { + vertical: VerticalAlign::Center, + horizontal: HorizontalAlign::Center, + }, + }, + ..default() + }); + }); +} + +fn button_handler( + mut interaction_query: Query< + (&Interaction, &mut UiColor), + (Changed, With