mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 22:18:33 +00:00
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
This commit is contained in:
parent
30ca97e287
commit
c4fc5d88f0
3 changed files with 89 additions and 7 deletions
|
@ -224,6 +224,11 @@ impl Touches {
|
||||||
self.pressed.get(&id)
|
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.
|
/// Returns `true` if the input corresponding to the `id` has just been pressed.
|
||||||
pub fn just_pressed(&self, id: u64) -> bool {
|
pub fn just_pressed(&self, id: u64) -> bool {
|
||||||
self.just_pressed.contains_key(&id)
|
self.just_pressed.contains_key(&id)
|
||||||
|
@ -239,6 +244,11 @@ impl Touches {
|
||||||
self.just_released.get(&id)
|
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.
|
/// Returns `true` if the input corresponding to the `id` has just been released.
|
||||||
pub fn just_released(&self, id: u64) -> bool {
|
pub fn just_released(&self, id: u64) -> bool {
|
||||||
self.just_released.contains_key(&id)
|
self.just_released.contains_key(&id)
|
||||||
|
@ -249,6 +259,11 @@ impl Touches {
|
||||||
self.just_released.values()
|
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.
|
/// Returns `true` if the input corresponding to the `id` has just been cancelled.
|
||||||
pub fn just_cancelled(&self, id: u64) -> bool {
|
pub fn just_cancelled(&self, id: u64) -> bool {
|
||||||
self.just_cancelled.contains_key(&id)
|
self.just_cancelled.contains_key(&id)
|
||||||
|
@ -259,6 +274,11 @@ impl Touches {
|
||||||
self.just_cancelled.values()
|
self.just_cancelled.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the position of the first currently pressed touch, if any
|
||||||
|
pub fn first_pressed_position(&self) -> Option<Vec2> {
|
||||||
|
self.pressed.values().next().map(|t| t.position)
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes a [`TouchInput`] event by updating the `pressed`, `just_pressed`,
|
/// Processes a [`TouchInput`] event by updating the `pressed`, `just_pressed`,
|
||||||
/// `just_released`, and `just_cancelled` collections.
|
/// `just_released`, and `just_cancelled` collections.
|
||||||
fn process_touch_event(&mut self, event: &TouchInput) {
|
fn process_touch_event(&mut self, event: &TouchInput) {
|
||||||
|
|
|
@ -71,10 +71,6 @@ pub fn ui_focus_system(
|
||||||
Option<&CalculatedClip>,
|
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
|
// reset entities that were both clicked and released in the last frame
|
||||||
for entity in state.entities_to_reset.drain(..) {
|
for entity in state.entities_to_reset.drain(..) {
|
||||||
if let Ok(mut interaction) = node_query.get_component_mut::<Interaction>(entity) {
|
if let Ok(mut interaction) = node_query.get_component_mut::<Interaction>(entity) {
|
||||||
|
@ -83,7 +79,7 @@ pub fn ui_focus_system(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouse_released =
|
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 {
|
if mouse_released {
|
||||||
for (_entity, _node, _global_transform, interaction, _focus_policy, _clip) in
|
for (_entity, _node, _global_transform, interaction, _focus_policy, _clip) in
|
||||||
node_query.iter_mut()
|
node_query.iter_mut()
|
||||||
|
@ -97,7 +93,12 @@ pub fn ui_focus_system(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouse_clicked =
|
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
|
let mut moused_over_z_sorted_nodes = node_query
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|
|
@ -13,13 +13,14 @@ fn main() {
|
||||||
.add_startup_system(setup_scene)
|
.add_startup_system(setup_scene)
|
||||||
.add_startup_system(setup_music)
|
.add_startup_system(setup_music)
|
||||||
.add_system(touch_camera)
|
.add_system(touch_camera)
|
||||||
|
.add_system(button_handler)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn touch_camera(
|
fn touch_camera(
|
||||||
windows: ResMut<Windows>,
|
windows: ResMut<Windows>,
|
||||||
mut touches: EventReader<TouchInput>,
|
mut touches: EventReader<TouchInput>,
|
||||||
mut camera: Query<&mut Transform, With<Camera>>,
|
mut camera: Query<&mut Transform, With<Camera3d>>,
|
||||||
mut last_position: Local<Option<Vec2>>,
|
mut last_position: Local<Option<Vec2>>,
|
||||||
) {
|
) {
|
||||||
for touch in touches.iter() {
|
for touch in touches.iter() {
|
||||||
|
@ -47,6 +48,7 @@ fn setup_scene(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
// plane
|
// plane
|
||||||
commands.spawn_bundle(PbrBundle {
|
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),
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
..default()
|
..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<Interaction>, With<Button>),
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
for (interaction, mut color) in interaction_query.iter_mut() {
|
||||||
|
match *interaction {
|
||||||
|
Interaction::Clicked => {
|
||||||
|
*color = Color::BLUE.into();
|
||||||
|
}
|
||||||
|
Interaction::Hovered => {
|
||||||
|
*color = Color::GRAY.into();
|
||||||
|
}
|
||||||
|
Interaction::None => {
|
||||||
|
*color = Color::WHITE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_music(asset_server: Res<AssetServer>, audio: Res<Audio>) {
|
fn setup_music(asset_server: Res<AssetServer>, audio: Res<Audio>) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue