#bevy-ui-navigation

121 messages Β· Page 1 of 1 (latest)

elfin nest
proven atlas
#

so hello there guys

#

I have a question about RenderLayers and navigation

#

I managed to create my mouse system that takes into account focusables that are rendered by current camera, but from what I have seen I cannot really do it with keyboard

merry shell
#

Oh noes RenderLayers πŸ™ˆ

proven atlas
#

if I'm thinking right I'd need to have custom fnresolve -> NavEvent

merry shell
#

hmm you could use the Focusable::block feature to prevent navigation to invisible entities

proven atlas
merry shell
#

Ohh, are those different menus?

proven atlas
#

yep, they live in separate world

#

bad choice of words, but those are not related

merry shell
#

Then you could use the MenuBuilder feature. It allows to group collections of focusables within separate menus, navigation will take the menu into account

#

Though it does require fitting it in the bevy hierarchy.

proven atlas
#

From what I've seen there's a lot of components to insert to make it work

#

But I guess I'll have to go that way, because there probably will be some another menus here and there

merry shell
#

The complex component setup is more or less optional, but it's useful if you want to react to menu changes.

proven atlas
#

It was optional until I wanted to separate battle view from rest of the game 😁

#

And I wanted to make this separation very separate, because the rest of the game rn is start button bavy

proven atlas
#

hmm now I wonder if I can make such menu if I have focusable entities, which are not an UINode

untold bough
#

Hello, I tried to add touch support by changing the generic_default_mouse_input to generic_default_pointer_input and then extracting eveything specific about the mouse and the touches to default_mouse_input and default_touch_input here : https://github.com/VincentDouchin/ui-navigation/blob/master/src/systems.rs It works on double tap when emulating a phone on the browser but not when I try it on my phone, can someone give me a hand?

GitHub

A UI navigation algoritm implemented in rust currently targeting the bevy ui library - ui-navigation/src/systems.rs at master Β· VincentDouchin/ui-navigation

merry shell
#

I saw your code earlier. I'm not in a position to help, I don't have a device handy to test it out.

untold bough
#

that's fine, I'll fiddle with it until I get something working, thanks anyway πŸ™‚

merry shell
#

@untold bough have you considered hooking bevy_mod_picking and make it emit NavRequests instead of re-implementing touch input handling?

#

This was the direction I meant to steer bevy-ui-navigation toward. mouse input is very much an afterthought on bevy-ui-navigation, and integrating with a crate that specializes in it seems the best way.

untold bough
#

@merry shell now that you mention it I think I actually went down the wrong rabbit hole, I might just hook the Interaction component to the correct NavRequest events and only keep the gamepad UI navigation

merry shell
#

yeah sorry for not mentioning it earlier. I only thought about it after a few hours

untold bough
#

No worries, I'm just glad you mentioned it and made this awesome crate! I implemented UI navigation in a previous project and it was a pain so I was really happy to see that a solution already existed for bevy πŸ™‚

untold bough
#

I actually figured it out, the problem was when we release a touch we don't know where it went and so hovering is false, I added a local variable to keep track of the position and it now works as intended

main ibex
#

Is activated_in_query basically the same as Query::iter_many?

merry shell
#

yeah. Very much so.

#

Let me find the code.

vapid isle
#

Moved from #ui
Still not sure I'm grokking the right way to do this. I'm trying to make a character selection screen, with the playable characters as buttons/icons at the bottom of the screen.
The compiler diagnostic is telling me

error[E0061]: this method takes 4 arguments but 2 arguments were supplied
  --> src/character_select.rs:93:29
   |
92 | /                         dsl! {&mut commands,
93 | |                             button(name, text_style, Square, named name);
   | |                             ^^^^^^
94 | |                         }
   | |_________________________- two arguments of type `bevy::prelude::TextStyle` and `ButtonStyle` are missing
   |
note: method defined here
  --> src/character_select.rs:39:8
   |
39 |     fn button(&mut self, text: String, text_style: TextStyle, style: ButtonStyle,  cmds: &mut EntityCommands) -> Entity {
   |        ^^^^^^            ------------  ---------------------  ------------------   -------------------------
help: provide the arguments
  --> /Users/ajt/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cuicui_dsl-0.8.1/src/macros.rs:472:11
   |
472|         x.button(name, /* bevy::prelude::TextStyle */, /* ButtonStyle */, &mut leaf_cmd)
   |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It looks to me like I'm passing all 4 arguments, any pointers?

merry shell
#

f give me a sec I closed the tab and now my message is deleted

vapid isle
#

(Thank you for helping twice in that case πŸ˜‰ )

merry shell
#

What's the name of the DslBundle with the button method?

vapid isle
#

MenuDsl

merry shell
#

So you have two ways to fix this.

  1. Split the arguments in several methods
  2. pass a tuple as first argument
    I strongly recommend (1)

1. Split the arguments in several methods

So your DslBundle is meant to accumulate style arguments and build things at the very end.

Instead of passing all the arguments at the same time in the leaf node, you should pass them progressively one after the others, it accumulates the arguments until the end, when button will be called with the struct with the accumulated values and the initial arguments:

#[derive(Deref, DerefMut, Default)]
struct MenuDsl<T = ()> {
    #[deref]
    inner: T,
    text_style: TextStyle,
    button_style: ButtonStyle,
}
impl<T: DslBundle> MenuDsl<T> {
    fn button_style(&mut self, style: ButtonStyle) {
        self.button_style = style;
    }
    fn text_style(&mut self, style: TextStyle) {
        self.text_style = style;
    }
    fn button(&mut self, text: String, cmds: &mut EntityCommands) -> Entity {
        // spawn button
    }
}
// ...
button(name, text_style text_style, button_style Square, named name);

2. pass a tuple as first argument

It should always be preferred to use (1). The leaf nodes was a way to enable spawn_ui which can't really work without it.

But you can abuse them using tuples like so:

    fn button(
        &mut self,
        (text, button_style, text_style): (String, ButtonStyle, TextStyle),
        cmds: &mut EntityCommands,
    ) -> Entity {
        // spawn button
    }
// call it this way:
button((name, text_style, Square), named name);
vapid isle
#

Just digesting #1, but I really appreciate the breakdown. This is awesome/mind blowing.

merry shell
#

If you are lazy and don't want to define methods you can also do

button(name, .text_style text_style, .button_style Square, named name);
``` But I'm not 100% sure I want to keep this way of doing.
vapid isle
vapid isle
#

Ah, I think it was a rust misunderstanding on my part. So I end up with (UiDsl or BaseDsl, I'm also using things like row, etc).

struct MenuDsl<T = UiDsl> {...}

Codebase is feeling good now, thanks!

vapid isle
#

Me again! Hopefully a quick question. I'm implementing a character selection screen (in my game the layout is the top two thirds focusing on the focused character, and the bottom third with characters to pick from). I'm wondering if marking is the example I should be looking at. As it seems to highlight in the left panel what is selected in the right panel. https://github.com/nicopap/ui-navigation/blob/master/examples/marking.rs

merry shell
vapid isle
#

I think I butchered your suggestion. I have the following enum Character is a struct and I have 2 const's that are characters. The reason I think I butchered it (or maybe I'm missing a rust trick), is now when I iterate over characters I have to match them to get to the 'Character' type

#[derive(Component, Clone, Debug)]
enum PlayerSelection {
    FirstCharacter(Character),
    SecondCharacter(Character),
}
pub const CHARACTERS: [PlayerSelection; 2] = [
    PlayerSelection::FirstCharacter(FIRST_CHARACTER),
    PlayerSelection::SecondCharacter(SECOND_CHARACTER),
]
merry shell
#

So "selected character" is not something inherent to an entity. It's just a game state. So I'd make it a resource. What I meant by PlayerSelection is "which character will be selected if this button is activated"

#
#[derive(Component, Clone, Copy)]
enum Character {
  Crystal,
  Fish,
  Eyes,
  Melting,
}
#[derive(Resource)]
struct ChoosenCharacters {
  primary: Option<Character>,
  secondary: Option<Character>,
}
fn select_character(
  mut events: EventReader<NavEvent>,
  character_buttons: Query<&Character>,
  mut choosen: ResMut<ChoosenCharacters>,
) {
  for character in events.nav_iter().activated_in_query(&character_buttons) {
    choosen.primary = Some(*character);
  }
}

fn setup(mut cmds: Commands) {
  use Character::*;

  for to_spawn in [Crystal, Fish, Eyes, Melting] {
    cmds.spawn((FocusableButtonBundle::default(), to_spawn));
  }
}
``` Something like that maybe? (not including the `dsl!` macro)
#

I'm not sure what "first character" and "second character" means here.

vapid isle
#

Oh, I think I understand now. first_character was just a name of the first playable character. Like you might have Ken in Street Fighter

vapid isle
merry shell
vapid isle
#

Almost certainly a bug in my code - I'm new to bevy/game dev

vapid isle
#

When you say

Try reading directly the NavEvent to see what's going on
How would I do that? And what should I be looking for?

merry shell
#

Just use events.iter() instead of events.nav_iter(), you can then dbg!(character) or println! the event

vapid isle
#

Just printing it out I get things like this (which seem reasonable, there's initial focus on one item and I moved to another)

InitiallyFocused(10v0)
FocusChanged { to: NonEmpty([12v0]), from: NonEmpty([10v0]) }
merry shell
#

what happens when you "select" a specific character?

vapid isle
#
NoChanges { from: NonEmpty([16v0]), request: Action }

If I'm reading it right

merry shell
#

I see. So you spawn the button with the Character component right?

#

Try doing a

if let NavEvent::NoChanges { from, .. } = event {
  dbg!(character_buttons.get(from.first()));
}
``` inside the for loop
vapid isle
#

So you spawn the button with the Character component right?
I might actually be an idiot. Thank you for your patience

merry shell
#

so that last bit of code I sent is basically what activated_in_query does

vapid isle
#

Putting in that code snippet I get Err(QueryDoesNotMatch(18v0)). I'm fairly certain the button is spawned with the character now. I added this snippet in the dsl, and create the button with button(img.into(), character c)

    fn character(&mut self, character: &Character) {
        self.character = *character;
    }
#

My hunch is that it's something to do with pointers

#

No, wait, I still think the button I've spawned isn't connected to the character πŸ€¦β€β™‚οΈ

vapid isle
#

I got this after some refactoring on the dbg! line you suggested earlier. However that doesn't seem to translate into activated_in_query triggering

Ok(Ken)
mystic forge
#

Wonderful library @merry shell . I'm of the opinion that this should be built into bevy.
I've figured out how to use the MenuBuilder, and I created some menus.

I can navigate into the settings menu using keyboard, controller, and mouse, and the focus transitions and doesn't transition in all the ways I expect.

The only problem is that the settings menu is scrunched up to the side there, all the time.
Is there a convenient way to hide the settings menu when it's not in use? I didn't see an obvious way to do it and started to write a system to hide menus that don't contain any focused elements, but that feels complicated for a problem I suspect will be a common use case.

merry shell
# mystic forge Wonderful library <@77068961144909824> . I'm of the opinion that this should be ...

So what you want is set the Style.display field of the root node of the menu you want to hide to Display::None whenever you leave it (and set it to Display::Flex when you enter it)

You have two ways of doing it:

  1. listen to NavEvent::FocusChanged, check for navigation buttons in the from and to field (here it would be the entity for the Settings button) and change the menu root display value accordingly
  2. Setup NavMarkers for your menus so that it's easy to use a Query to see if a menu was focused (check the marking.rs example)
mystic forge
#

Okay, I had figured out the Style.display part. I had a feeling NavMarker was meant to be a solution to this but wasn't grasping how.
Let me experiment with that a little.

mystic forge
#

Took me a few tries but I figured this out:

fn hide_when_unfocused(
    menu_items: Query<(), With<MainMenuMarker>>,
    mut main_menu_styles: Query<&mut Style, With<MainMenu>>,
    mut removed: RemovedComponents<Focused>,
) {
    for removal in removed.iter() {
        if menu_items.contains(removal) {
            for mut menu_style in main_menu_styles.iter_mut() {
                menu_style.display = Display::None;
            }
        }
    }
}
fn show_when_focused(
    menu_items: Query<(), (Added<Focused>, With<MainMenuMarker>)>,
    mut main_menu_styles: Query<&mut Style, With<MainMenu>>,
) {
    if menu_items.get_single().is_ok() {
        for mut menu_style in main_menu_styles.iter_mut() {
            menu_style.display = Display::Flex;
        }
    }
}

It's very important that show_when_focused runs after hide_when_unfocused, otherwise switching to elements within the same menu will cause the menu to vanish.

I'm going to try and generic these up so I don't have to rewrite all this for every single menu.
Thanks for the tip.

merry shell
#

Hopefully relations can make this simpler in the future. Since NavMarker is just a poor man's way of setting up a relation

mystic forge
#

I've already written it, and here it is, for anyone to copy:

pub fn setup_submenu<Menu: Component, Marker: Component + Clone>(app: &mut App) {
    fn hide_when_unfocused<Menu: Component, Marker: Component + Clone>(
        menu_items: Query<(), With<Marker>>,
        mut menu_styles: Query<&mut Style, With<Menu>>,
        mut removed: RemovedComponents<Focused>,
    ) {
        for removal in removed.iter() {
            if menu_items.contains(removal) {
                for mut menu_style in menu_styles.iter_mut() {
                    menu_style.display = Display::None;
                }
            }
        }
    }
    fn show_when_focused<Menu: Component, Marker: Component + Clone>(
        menu_items: Query<(), (Added<Focused>, With<Marker>)>,
        mut menu_styles: Query<&mut Style, With<Menu>>,
    ) {
        if menu_items.get_single().is_ok() {
            for mut menu_style in menu_styles.iter_mut() {
                menu_style.display = Display::Flex;
            }
        }
    }

    app.add_plugins(NavMarkerPropagationPlugin::<Marker>::new());
    app.add_systems(
        Update,
        (
            hide_when_unfocused::<Menu, Marker>.after(NavRequestSystem),
            show_when_focused::<Menu, Marker>
                .after(hide_when_unfocused::<Menu, Marker>)
                .after(NavRequestSystem),
        ),
    );
}
#

It still requires you to create your own Menu component, so it knows what to set the display mode on, and your own Marker component.
And of course, you need to all all of these to the top level entity in your whole menu tree.

#

But hey, that boiled it down to like, six lines of code to set this up for a menu. That's a pretty big improvement. I'll take it.

mystic forge
#

I've created a custom widget for inputting numbers.
You can skip the following line if you don't care about that and just want to get to the problem I've encountered, but I'm explaining how the widget works just in case there's a way easier way to do what I'm doing.
When you select the widget you can focus on the individual numbers and press the up/down button on the keyboard or controller to increment/decrement that specific number.
Here's a video of what I have in action:

#

To try and keep things simple, I'm just listening for NavRequest events in a system, which then increments/decrements the digit depending on what's focused.

#

My issue is that for some reason I'm getting duplicate events between multiple frames, as in an event that happens in frame 1 will repeat in frame 2.
I suspect my system is being ran at the wrong time. Is there a system I'm supposed to run before or after?

#

Currently I have it running after NavRequestSystem.

#

My current system setup for context:

fn process_digit_input(
    // I grab the resource because I want to send a Cancel when "accept" is pressed.
    // That'll cause the user to exit the sub-menu for setting digits.
    mut nav_requests: ResMut<Events<NavRequest>>,

    /* Arguments specific to setting the number omitted */
) {
  /* The body */
}
app.add_systems(Update, process_digit_input.after(NavRequestSystem));
mystic forge
#

By just experimenting I found that adding it to PreUpdate makes the issue go away.
I'm a little uncomfortable with that though because I don't have a great grasp as to why that would be. Am I looking at events from the previous frame?

mystic forge
#

Well I eventually figured out that I was doing this the completely wrong way.

#

I shouldn't be reading from NavRequests, I should be reading from NavEvents. Those events include the NavRequests within them, but without the duplication.

#

Even when part of the normal Update stage.

merry shell
#

hmm. I've been assuming that the user listens to NavEvent and emits NavRequest. The issue with having users is they show you how bad any assumption is πŸ˜… Let me read this more attentively

#

So right now there is only one system that listens to NavRequest events, it unconditionally consumes all NavRequests, and it's in the NavRequestSystem set, so you are good on this front.

#

I'm a bit of a loss here. So to clarify we have:

  1. A system that emits NavRequest associated with actual user input
  2. A system that listens to NavEvents and sets the number based on (supposedly) NavEvent::NoChanges, but also can emit NavRequest::Cancels
mystic forge
#

Yeah, that's exactly what I have now.

#

I wanted to make it so you'd return back to the menu you started editing the number from when you pressed Accept.

#

It's pretty close to finished now. All that's left is to add copy/paste support and a way to enforce min/max values.

#

Give me a few minutes. I can show you a video demo of the finished thing.

#

You can use the arrow keys to input values, you can type out the number, you can hover over the digits with your mouse and scroll to set the number, or you can use a gamepad to set the number.

#

The mouse is probably the least usable of all those input methods, since the digits are kinda small.

merry shell
#

On a side-note: I've meant to remove mouse support from ui-navigation for the longest time. pointer-based input is just too different from key-based input. Ideally you'd use a different crate (like bevy_mod_picking) to handle mouse input.

#

Let's call the system that emits NavRequest based on actual input sys_input. And the system that sends NavRequest::Cancel sys_cancel. You've ordered sys_input as sys_input.before(NavRequestSystem) right? Not that I think it should cause the issue you are seeing

#

Also consider looking at the locking.rs example. The locking mechanism was designed to help handle input-field type things.

mystic forge
#

Oh, I've already fixed the issue buy reading Events instead of NavRequests. I realized I was using that wrong.

#

I can't remember why I didn't go with the locking approach. I should check that again.

mystic forge
merry shell
#

The current mouse input handling in ui-navigation is very hacky. And ui-navigation is about navigating menus with gamepad/keyboard. I don't want to implement proper mouse handling, since other people already did it and it is surprisingly complex. Ideally I would integrate with bevy_mod_picking rather than reinvent mouse picking myself.

mystic forge
#

Okay, that sounds reasonable.

#

Just as long as I can keep some form of mouse based navigation.

merry shell
#

@mystic forge @untold bough I've published version 0.31.0 of bevy-ui-navigation. I've replaced the custom mouse picking impl by bevy_mod_picking. From my testing, it handles much better bevy_ui and it should work out of the box with touch input.

untold bough
mystic forge
#

Oh fancy. I'll need some time before I get to it.

mystic forge
#

I finally updated (yes I know an entire month later).
Well it seems to work just the same, so no complaints here.

#

I've encounter a minor issue. Not sure if it's an oversight or I'm using the library wrong.
I set my engine up with a bevy_console.

Typing on the keyboard causes the UI in the background to start triggering.
I added this system to fix that:

        app.add_systems(
            Update,
            |mut ui_input_mapping: ResMut<InputMapping>, console_open_state: Res<ConsoleOpen>| {
                // Disables the keyboard if the console is open.
                ui_input_mapping.keyboard_navigation = !console_open_state.open;
            },
        );

That mostly works but the cancel and action keys are still going through.
Were those supposed to be disabled with the rest of the keyboard navigation?

#

A workaround wouldn't be hard, I just don't want to do that if there's a more proper solution.

#

For clarity, this is my workaround:

        app.add_systems(
            Update,
            |mut ui_input_mapping: ResMut<InputMapping>, console_open_state: Res<ConsoleOpen>| {
                // Disables the keyboard if the console is open.
                ui_input_mapping.keyboard_navigation = !console_open_state.open;

                if console_open_state.open {
                    ui_input_mapping.key_action = KeyCode::Unlabeled;
                    ui_input_mapping.key_cancel = KeyCode::Unlabeled;
                } else {
                    ui_input_mapping.key_action = KeyCode::Return;
                    ui_input_mapping.key_cancel = KeyCode::Escape;
                }
            },
        );
#

Not exactly what I'd call pretty.

merry shell
#

The idea is specifically to allow pressing a button (backspace/escape) when someone expects to affect menu navigation, while disabling default handling of keyboard based navigation (arrow keys/wasd). So it's intended behaviorβ„’

#

I've been using ui-navigation more extensively and I still think there is still a lot of improvements left to do. I think I might be able to implement it as a backend to bevy_mod_picking somehow, and it would make reacting to navigation events more ergonomic

mystic forge
#

No worries, that design choice makes sense.

#

I'm actually thinking about overhauling my UI system because it's a pain to use in its current state.

#

Not for the user to use, but for me to make UI with.

white abyss
#

When despawning my ui, sometimes my game crashes due to commands.rs line 14 as there is no entity when command is ran.

let mut entity = world.entity_mut(self.entity);

Is there a way to order systems to not have this or another way to avoid this?

white abyss
#

ah, I was accidentally despawning the ui too fast after spawning it

scenic thunder
#

I'm trying out this crate and it mostly seems to make sense except for how exactly I'm supposed to respond to the action button.

  • am I supposed listening to NavEvent::NoChange { entity, Action } and respond to that?
  • It doesn't feed back into the built-in Interaction logic does it?

It's fine if those two statements are true, but I'd love it if there was a separate NavEvent::Action { entity } rather than re-using NoChange lol, the naming is confusing. I also didn't see any example that actual responds to Action that explained the expected usage

merry shell
#

Yep. The bevy Interaction is not well suited to input management. In fact the plan is to replace it with an event-based system in the long term.

It is correct, you are meant to use NavEvent::NoChange.

There is a trait that should help though, the EventReader<NavEvent> type has a trait extension method nav_iter() that returns a NavEventReader which has a few convenience methods that should suit you https://docs.rs/bevy-ui-navigation/latest/bevy_ui_navigation/events/struct.NavEventReader.html

#

@scenic thunder ^

scenic thunder
#

Ahh ok, that looks more straightforward. Thanks!