#bevy-ui-navigation
121 messages Β· Page 1 of 1 (latest)
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
Oh noes RenderLayers π
if I'm thinking right I'd need to have custom fnresolve -> NavEvent
hmm you could use the Focusable::block feature to prevent navigation to invisible entities
yeah it makes more sense to despawn camera for a moment rather than despawn all entites π
Ohh, are those different menus?
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.
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
The complex component setup is more or less optional, but it's useful if you want to react to menu changes.
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 
hmm now I wonder if I can make such menu if I have focusable entities, which are not an UINode
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?
I saw your code earlier. I'm not in a position to help, I don't have a device handy to test it out.
that's fine, I'll fiddle with it until I get something working, thanks anyway π
@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.
@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
yeah sorry for not mentioning it earlier. I only thought about it after a few hours
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 π
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
Is activated_in_query basically the same as Query::iter_many?
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?
f give me a sec I closed the tab and now my message is deleted
(Thank you for helping twice in that case π )
What's the name of the DslBundle with the button method?
MenuDsl
So you have two ways to fix this.
- Split the arguments in several methods
- 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);
Just digesting #1, but I really appreciate the breakdown. This is awesome/mind blowing.
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.
Hopefully last question, I was adapting this example: https://github.com/nicopap/ui-navigation/blob/master/examples/ultimate_menu_navigation.rs#L36-L41
the self.0.insert(cmds) had to be changed. I replaced 0 with inner, and the compilation errors in that area went away. However I'm seeing things like
no method named `named` found for struct `MenuDsl` in the current scope
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!
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
I would personally create a PlayerSelection component, an enum where each variant is a character. Then spawn the menu buttons with the component in question. Then through nav_iter().activated() (https://docs.rs/bevy-ui-navigation/latest/bevy_ui_navigation/events/trait.NavEventReaderExt.html) I'd listen to navigation events, check the PlayerSelection of "activated" entities)
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),
]
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.
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
I'm almost there. During debugging the select_character function is triggering, but not the inner loop (the activated_by_query bit). I'm also using something like this button_system which is working, so I can see the focus changing from one character to another https://github.com/nicopap/ui-navigation/blob/9c82db6e563d5cbb87941a30d39ee46dfda2c9c7/examples/ultimate_menu_navigation.rs#L102 .
Hopefully this isn't a bug in activated_in_query. Try reading directly the NavEvent to see what's going on
Almost certainly a bug in my code - I'm new to bevy/game dev
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?
Just use events.iter() instead of events.nav_iter(), you can then dbg!(character) or println! the event
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]) }
what happens when you "select" a specific character?
NoChanges { from: NonEmpty([16v0]), request: Action }
If I'm reading it right
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
So you spawn the button with the Character component right?
I might actually be an idiot. Thank you for your patience
so that last bit of code I sent is basically what activated_in_query does
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 π€¦ββοΈ
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)
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.
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:
- listen to
NavEvent::FocusChanged, check for navigation buttons in thefromandtofield (here it would be the entity for theSettingsbutton) and change the menu root display value accordingly - Setup
NavMarkers for your menus so that it's easy to use aQueryto see if a menu was focused (check themarking.rsexample)
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.
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.
Hopefully relations can make this simpler in the future. Since NavMarker is just a poor man's way of setting up a relation
Something in the style of the cheat book's "generic system" would be a good starting point https://bevy-cheatbook.github.io/patterns/generic-systems.html
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.
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));
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?
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.
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:
- A system that emits
NavRequestassociated with actual user input - A system that listens to
NavEvents and sets the number based on (supposedly)NavEvent::NoChanges, but also can emitNavRequest::Cancels
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.
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.
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.
I get a little nervous hearing that because I've been making quite a bit of use of mouse based navigation... This game is an FPS (A Voxel engine to be exact) so not forcing the player to take their hands off the mouse all the time is nice.
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.
Okay, that sounds reasonable.
Just as long as I can keep some form of mouse based navigation.
@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.
Wonderful! I'll give it a try π
Oh fancy. I'll need some time before I get to it.
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.
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
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.
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?
ah, I was accidentally despawning the ui too fast after spawning it
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
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 ^
Ahh ok, that looks more straightforward. Thanks!