Multiple buttons without multiple button systems?

784 views Asked by At

I'm fairly new to rust and newer to bevy. I realize it's the early days, and like bevy a lot, but I frankly find examples and docs a bit lacking.

I use the /examples/ui/button.rs as a starting point. And I want to add a bunch of buttons instead of just one.

I'm looking for a way to distinguish what button was clicked.

I realize I could add a system for each button, but that can't be the right way in any language.

So, I started adding markers (like the bevy-cheatsheet suggests).

commands
    .spawn(ButtonComponents { /* cut for brevity */ })
    .with_children(|parent| {
        parent
            .spawn(TextComponents {  /* cut for brevity */ })
            .with(Marker1);
    });

But how do I then go about to check what marker a button has been spawned with?

fn button_system(
    button_materials: Res<ButtonMaterials>,
    mut interaction_query: Query<(
        &Button,
        Mutated<Interaction>,
        &mut Handle<ColorMaterial>,
        &Children,
    )>,
    text_query: Query<&mut Text>,
) {
    for (_button, interaction,  mut material, children) in &mut interaction_query.iter() {
        let mut text = text_query.get_mut::<Text>(children[0]).unwrap();
        match *interaction {
            Interaction::Clicked => {            

                // This obviously doesn't work, just to illustrate what I'm looking for.
                match text.spawned_with {
                    Marker1 => doSomething(),
                    Marker2 => doBarrelRoll(),
                    _ => unreachable!()
                }    

            }
            Interaction::Hovered => {
                text.value = "Hover".to_string();
                *material = button_materials.hovered.clone();
            }
            Interaction::None => {
                text.value = "Button".to_string();
                *material = button_materials.normal.clone();
            }
        }
    }
}

Any hints are welcome, thanks!

Edit: And now I'm confused, because this actually works for button 1, (but crashes button2):

Interaction::Clicked => {
    let marker = text_query.get::<_>(children[0]).unwrap();
    match *marker {
        Marker1 => println!("marker 1"),
        _ => unreachable!(),
    }
}

But this doesn't even build:

    let marker = text_query.get::<_>(children[0]).unwrap();
    match *marker {
        Marker1 => println!("marker 1"),
        Marker2 => println!("marker 2"),
        _ => unreachable!(),
    }

This is the error:

   |                     expected struct `Marker1`, found struct `Marker2`
   |                     `Marker2` is interpreted as a unit struct, not a new binding
   |                     help: introduce a new binding instead: `other_marker2`
2

There are 2 answers

2
ippi On BEST ANSWER

Alright, I found an answer, but if you have something more elegant I'd be happy to learn and make that the correct answer instead!

Interaction::Clicked => {
    if let Ok(_) = text_query.get::<Marker1>(children[0]) {
        println!("marker 1")
    }
    if let Ok(_) = text_query.get::<Marker2>(children[0]) {
        println!("marker 2")
    }
    if let Ok(_) = text_query.get::<Marker3>(children[0]) {
        doBarrelRoll()
    }
    ...
}

After daniel kullmans comment I ended up with something like this instead:

#[derive(PartialEq)]    // needed for comparison
pub enum Buttons {
    MyFirstButton,
    MySecondButton,
}
struct MyButton {
    target: Buttons,
}

And...

commands
    .spawn(ButtonComponents { /* cut for brevity */ })
    .with_children(|parent| {
        parent
            .spawn(TextComponents {  /* cut for brevity */ })
            .with(MyButton { target: Buttons }); 
    });

And...

Interaction::Clicked => {
    if let Ok(btn) = text_query.get_mut::<MyButton>(children[0]) {
        match btn.target {
            Buttons::MyFirstButton => {
               ...
            },
            Buttons::MySecondButton => {
               ...
            },
            _ => unreachable!(),
        }
    }
}
0
robng On

Although I'm almost two years late to the discussion, I came across the same problem today and I think I found a nice solution. I took some inspiration from @ippi's original post, where they used empty marker structs. These structs are also described here: https://bevy-cheatbook.github.io/programming/queries.html.

So let's assume we have two buttons in a UI. A start button and a quit button. First, we add marker structs for both buttons:

#[derive(Component)]
struct StartButtonMarker;

#[derive(Component)]
struct QuitButtonMarker;

Secondly, we spawn both buttons with their respective marker structs added to them as components (using insert).

commands.spawn_bundle(ButtonBundle { /* snip */ }).insert(StartButtonMarker);
commands.spawn_bundle(ButtonBundle { /* snip */ }).insert(QuitButtonMarker);

Now in our system, we can query both markers as optional values (using the Option<T> type). We can then check if a marker exists on the entity using the is_some function on the Option instance:

fn update(
    mut query: Query<
        (
            &Interaction,
            (Option<&StartButtonMarker>, Option<&QuitButtonMarker>),
        ),
        (Changed<Interaction>, With<Button>),
    >,
) {
    for (interaction, (marker_start, marker_quit)) in &mut query {
        if *interaction == Interaction::Clicked {
            if marker_start.is_some() {
                println!("Start button pressed");
            }
            if marker_quit.is_some() {
                println!("Quit button pressed");
            }
        }
    }
}