Skip to content

Commit b8190c0

Browse files
ManevilleFinodentry
authored andcommitted
Fixed bevy_ui touch input (bevyengine#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 (bevyengine#3249) - I don't have an iOS device
1 parent cf1d81d commit b8190c0

File tree

3 files changed

+89
-7
lines changed

3 files changed

+89
-7
lines changed

crates/bevy_input/src/touch.rs

+20
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ impl Touches {
224224
self.pressed.get(&id)
225225
}
226226

227+
/// Checks if any touch input was just pressed.
228+
pub fn any_just_pressed(&self) -> bool {
229+
!self.just_pressed.is_empty()
230+
}
231+
227232
/// Returns `true` if the input corresponding to the `id` has just been pressed.
228233
pub fn just_pressed(&self, id: u64) -> bool {
229234
self.just_pressed.contains_key(&id)
@@ -239,6 +244,11 @@ impl Touches {
239244
self.just_released.get(&id)
240245
}
241246

247+
/// Checks if any touch input was just released.
248+
pub fn any_just_released(&self) -> bool {
249+
!self.just_released.is_empty()
250+
}
251+
242252
/// Returns `true` if the input corresponding to the `id` has just been released.
243253
pub fn just_released(&self, id: u64) -> bool {
244254
self.just_released.contains_key(&id)
@@ -249,6 +259,11 @@ impl Touches {
249259
self.just_released.values()
250260
}
251261

262+
/// Checks if any touch input was just cancelled.
263+
pub fn any_just_cancelled(&self) -> bool {
264+
!self.just_cancelled.is_empty()
265+
}
266+
252267
/// Returns `true` if the input corresponding to the `id` has just been cancelled.
253268
pub fn just_cancelled(&self, id: u64) -> bool {
254269
self.just_cancelled.contains_key(&id)
@@ -259,6 +274,11 @@ impl Touches {
259274
self.just_cancelled.values()
260275
}
261276

277+
/// Retrieves the position of the first currently pressed touch, if any
278+
pub fn first_pressed_position(&self) -> Option<Vec2> {
279+
self.pressed.values().next().map(|t| t.position)
280+
}
281+
262282
/// Processes a [`TouchInput`] event by updating the `pressed`, `just_pressed`,
263283
/// `just_released`, and `just_cancelled` collections.
264284
fn process_touch_event(&mut self, event: &TouchInput) {

crates/bevy_ui/src/focus.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@ pub fn ui_focus_system(
7171
Option<&CalculatedClip>,
7272
)>,
7373
) {
74-
let cursor_position = windows
75-
.get_primary()
76-
.and_then(|window| window.cursor_position());
77-
7874
// reset entities that were both clicked and released in the last frame
7975
for entity in state.entities_to_reset.drain(..) {
8076
if let Ok(mut interaction) = node_query.get_component_mut::<Interaction>(entity) {
@@ -83,7 +79,7 @@ pub fn ui_focus_system(
8379
}
8480

8581
let mouse_released =
86-
mouse_button_input.just_released(MouseButton::Left) || touches_input.just_released(0);
82+
mouse_button_input.just_released(MouseButton::Left) || touches_input.any_just_released();
8783
if mouse_released {
8884
for (_entity, _node, _global_transform, interaction, _focus_policy, _clip) in
8985
node_query.iter_mut()
@@ -97,7 +93,12 @@ pub fn ui_focus_system(
9793
}
9894

9995
let mouse_clicked =
100-
mouse_button_input.just_pressed(MouseButton::Left) || touches_input.just_pressed(0);
96+
mouse_button_input.just_pressed(MouseButton::Left) || touches_input.any_just_pressed();
97+
98+
let cursor_position = windows
99+
.get_primary()
100+
.and_then(|window| window.cursor_position())
101+
.or_else(|| touches_input.first_pressed_position());
101102

102103
let mut moused_over_z_sorted_nodes = node_query
103104
.iter_mut()

examples/ios/src/lib.rs

+62-1
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ fn main() {
1313
.add_startup_system(setup_scene)
1414
.add_startup_system(setup_music)
1515
.add_system(touch_camera)
16+
.add_system(button_handler)
1617
.run();
1718
}
1819

1920
fn touch_camera(
2021
windows: ResMut<Windows>,
2122
mut touches: EventReader<TouchInput>,
22-
mut camera: Query<&mut Transform, With<Camera>>,
23+
mut camera: Query<&mut Transform, With<Camera3d>>,
2324
mut last_position: Local<Option<Vec2>>,
2425
) {
2526
for touch in touches.iter() {
@@ -47,6 +48,7 @@ fn setup_scene(
4748
mut commands: Commands,
4849
mut meshes: ResMut<Assets<Mesh>>,
4950
mut materials: ResMut<Assets<StandardMaterial>>,
51+
asset_server: Res<AssetServer>,
5052
) {
5153
// plane
5254
commands.spawn_bundle(PbrBundle {
@@ -86,6 +88,65 @@ fn setup_scene(
8688
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
8789
..default()
8890
});
91+
92+
// Test ui
93+
commands.spawn_bundle(Camera2dBundle::default());
94+
commands
95+
.spawn_bundle(ButtonBundle {
96+
style: Style {
97+
justify_content: JustifyContent::Center,
98+
align_items: AlignItems::Center,
99+
position_type: PositionType::Absolute,
100+
position: UiRect {
101+
left: Val::Px(50.0),
102+
right: Val::Px(50.0),
103+
top: Val::Auto,
104+
bottom: Val::Px(50.0),
105+
},
106+
..default()
107+
},
108+
..default()
109+
})
110+
.with_children(|b| {
111+
b.spawn_bundle(TextBundle {
112+
text: Text {
113+
sections: vec![TextSection {
114+
value: "Test Button".to_string(),
115+
style: TextStyle {
116+
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
117+
font_size: 30.0,
118+
color: Color::BLACK,
119+
},
120+
}],
121+
alignment: TextAlignment {
122+
vertical: VerticalAlign::Center,
123+
horizontal: HorizontalAlign::Center,
124+
},
125+
},
126+
..default()
127+
});
128+
});
129+
}
130+
131+
fn button_handler(
132+
mut interaction_query: Query<
133+
(&Interaction, &mut UiColor),
134+
(Changed<Interaction>, With<Button>),
135+
>,
136+
) {
137+
for (interaction, mut color) in interaction_query.iter_mut() {
138+
match *interaction {
139+
Interaction::Clicked => {
140+
*color = Color::BLUE.into();
141+
}
142+
Interaction::Hovered => {
143+
*color = Color::GRAY.into();
144+
}
145+
Interaction::None => {
146+
*color = Color::WHITE.into();
147+
}
148+
}
149+
}
89150
}
90151

91152
fn setup_music(asset_server: Res<AssetServer>, audio: Res<Audio>) {

0 commit comments

Comments
 (0)