Skip to content

Expose just_pressed-like functionality for button Interaction #2376

Closed
@alice-i-cecile

Description

@alice-i-cecile

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(),
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-UIGraphical user interfaces, styles, layouts, and widgetsC-FeatureA new feature, making something new possible

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions