#Bevy UI counter example - what's the idiomatic way of updating UI ?

16 messages · Page 1 of 1 (latest)

placid charm
#

So I am building a simple counter using bevy_ui. I got an implementation working, but it has a major painpoint, which is manually querying the entity tree to apply changes to the correct entity/component. Here is my code. I'd like to know if there is a way to improve :

  • The way the Counter component is updated when buttons are clicked
  • The way I track changes to the Counter and update the Text accordingly
#
#[derive(Component, Default)]
struct Counter {
    value: i32,
}

impl Counter {
    pub fn spawn() -> impl Bundle {
        (
            Self::default(),
            Node {
                justify_content: JustifyContent::Center,
                align_items: AlignItems::Center,
                width: Val::Percent(100.0),
                height: Val::Percent(100.0),
                ..Default::default()
            },
            children![
                (Button, children![Text::new("-")], observers![
                    |trigger: Trigger<Pointer<Click>>, world: &mut World| {
                        let parent = world.get::<ChildOf>(trigger.target()).unwrap().0;
                        let mut counter = world.get_mut::<Counter>(parent).unwrap();
                        counter.value -= 1;
                    }
                ]),
                Text::new("0"),
                (Button, children![Text::new("+")], observers![
                    |trigger: Trigger<Pointer<Click>>, world: &mut World| {
                        let parent = world.get::<ChildOf>(trigger.target()).unwrap().0;
                        let mut counter = world.get_mut::<Counter>(parent).unwrap();
                        counter.value += 1;
                    }
                ]),
            ],
        )
    }

    pub fn changed_system(
        query: Query<(Entity, &Counter), Changed<Counter>>,
        mut commands: Commands,
    ) {
        for (entity, counter) in query {
            let value = counter.value;

            commands.queue(move |world: &mut World| {
                let text = world.get_mut::<Children>(entity).unwrap()[1];
                world.get_mut::<Text>(text).unwrap().0 = value.to_string();
            });
        }
    }
}
#

Bevy UI counter example - what's the idiomatic way of updating UI ?

lyric stream
#

The easiest and probably most idiomatic solution would be to use an Observer

placid charm
#

can you elaborate a little please ? I am new to observers

lyric stream
#

Something along the lines of

fn update_text(trigger: Trigger<OnInsert, Counter>, text: Query<(&Counter, &mut Text)>) {
    let entity = trigger.target();
    let (counter, mut text) = text.get(entity).unwrap();
    text.0 = /* counter.to_string() */;
}
#

Though note that you should make Counter an immutable component for this to work

placid charm
#

yeah but the point is that Text is not on the same entity as Counter, I must query for the children

lyric stream
#

In that case just in a children: Query<&Children> and go through that

placid charm
#

if possible, I'm trying to avoid doing that, because that will break if I update the UI tree without updating children indexes as well

lyric stream
#

That's fair, maybe you need to add a custom relationship that tells your observer what target(s) should be updated by that Counter

placid charm
#

yeah that should do it, thank you very much

#

just a quick question, is there an equivalent for Query<(), Changed<T>> but with triggers ?

#

like a trigger that will run if the component was updated

#

there is OnInsert but I don't see OnChange

lyric stream
#

That doesn't exist, that's why it needs to be immutable