Closed
Description
What problem does this solve or what need does it fill?
Interaction, used for handling mouse interactions from buttons, only has three states: Clicked, Hovered and None.
This causes sustained clicks to repeatedly fire the button pressed, in a non-intuitive and typically incorrect way.
This is problematic when
What solution would you like?
Migrate to a per-entity events (#1626, #2116) model of handling UI inputs. As part of that, expose whether a click event is new.
What alternative(s) have you considered?
Add an internal timer or state to the Button manually?
Additional context
The example below demonstrates the problem, as the purple / not-purple state flickers rapidly if you click repeatedly.
use bevy::prelude::*;
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.init_resource::<WhiteMaterial>()
.init_resource::<PurpleMaterial>()
.add_startup_system(spawn_camera.system())
.add_startup_system(spawn_buttons.system())
.add_system(purplify_on_click.system())
.add_system(enforce_purple.system())
.run()
}
// These resources store persistent handles to our white and purple materials
// See the chapter on Assets for more details on how this works
struct WhiteMaterial(Handle<ColorMaterial>);
struct PurpleMaterial(Handle<ColorMaterial>);
impl FromWorld for WhiteMaterial {
fn from_world(world: &mut World) -> Self {
let mut materials = world.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
let handle = materials.add(Color::WHITE.into());
WhiteMaterial(handle)
}
}
impl FromWorld for PurpleMaterial {
fn from_world(world: &mut World) -> Self {
let mut materials = world.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
let handle = materials.add(Color::PURPLE.into());
PurpleMaterial(handle)
}
}
fn spawn_camera(mut commands: Commands) {
commands.spawn_bundle(UiCameraBundle::default());
}
fn spawn_buttons(mut commands: Commands) {
let button_transforms = vec![
Transform::from_xyz(-300.0, 0.0, 0.0),
Transform::from_xyz(0.0, 0.0, 0.0),
Transform::from_xyz(300.0, 0.0, 0.0),
];
commands.spawn_batch(button_transforms.into_iter().map(|transform| ButtonBundle {
// Each button has a unique transform, based in via .map
transform,
style: Style {
// Set button size
size: Size::new(Val::Px(150.0), Val::Px(150.0)),
// Center button
margin: Rect::all(Val::Auto),
..Default::default()
},
..Default::default()
}));
}
/// Simple marker component to dictate whether our button should be purple or not
struct Purple;
fn purplify_on_click(
query: Query<(Entity, &Interaction, Option<&Purple>)>,
mut commands: Commands,
) {
for (entity, interaction, maybe_purple) in query.iter() {
if *interaction == Interaction::Clicked {
// Adds or removes the Purple marker component when the entity is clicked
match maybe_purple {
// Adding components requires a concrete value for the new component
None => commands.entity(entity).insert(Purple),
// But removing them just requires the type
Some(_) => commands.entity(entity).remove::<Purple>(),
};
}
}
}
// This example is contrived for demonstration purposes:
// it would be much more efficient to simply set the material directly
// rather than using a marker component + system
fn enforce_purple(
mut query: Query<(&mut Handle<ColorMaterial>, Option<&Purple>)>,
white_material: Res<WhiteMaterial>,
purple_material: Res<PurpleMaterial>,
) {
for (mut material, maybe_purple) in query.iter_mut() {
*material = match maybe_purple {
None => white_material.0.clone(),
Some(_) => purple_material.0.clone(),
}
}
}