Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose just_pressed-like functionality for button Interaction #2376

Closed
alice-i-cecile opened this issue Jun 22, 2021 · 3 comments
Closed

Expose just_pressed-like functionality for button Interaction #2376

alice-i-cecile opened this issue Jun 22, 2021 · 3 comments
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible

Comments

@alice-i-cecile
Copy link
Member

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(),
        }
    }
}
@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets labels Jun 22, 2021
@alice-i-cecile
Copy link
Member Author

alice-i-cecile commented Jun 22, 2021

You can work around this to some degree with change detection. The new signature becomes:

fn purplify_on_click(
    query: Query<(Entity, &Interaction, Option<&Purple>), Changed<Interaction>,
    mut commands: Commands,
) {

@alice-i-cecile
Copy link
Member Author

That's... fine for now. Closing this out.

@ArmourStorm
Copy link

I created a small library to hopefully help with this.

Checkout: https://crates.io/crates/better_button

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible
Projects
None yet
Development

No branches or pull requests

2 participants