use bevy::prelude::*; use bevy::utils::HashSet; use bevy_egui::{EguiContexts, EguiPlugin, EguiSettings}; use bevy_egui_kbgp::egui; use bevy_egui_kbgp::prelude::*; #[derive(States, Default, Hash, Debug, PartialEq, Eq, Clone, Copy)] enum MenuState { #[default] Main, Empty1, Empty2, } #[derive(Clone, PartialEq, Eq)] enum MyActions { PrevMenu, NextMenu, Delete, } #[derive(PartialEq)] enum MyFocusLabel { PrevMenu, NextMenu, } fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_plugins(EguiPlugin); app.add_plugins(KbgpPlugin); app.insert_resource(EguiSettings { scale_factor: 1.5 }); app.insert_resource(KbgpSettings { disable_default_navigation: true, disable_default_activation: true, prevent_loss_of_focus: true, focus_on_mouse_movement: true, allow_keyboard: true, allow_mouse_buttons: true, allow_mouse_wheel: true, allow_mouse_wheel_sideways: true, allow_gamepads: true, bindings: { bevy_egui_kbgp::KbgpNavBindings::default() .with_wasd_navigation() .with_key(KeyCode::Space, KbgpNavCommand::Click) // Special actions - keyboard: .with_key(KeyCode::PageUp, KbgpNavCommand::user(MyActions::PrevMenu)) .with_key(KeyCode::PageDown, KbgpNavCommand::user(MyActions::NextMenu)) .with_key(KeyCode::Delete, KbgpNavCommand::user(MyActions::Delete)) // Special actions - gamepad: .with_gamepad_button( GamepadButtonType::LeftTrigger, KbgpNavCommand::user(MyActions::PrevMenu), ) .with_gamepad_button( GamepadButtonType::RightTrigger, KbgpNavCommand::user(MyActions::NextMenu), ) .with_gamepad_button( GamepadButtonType::North, KbgpNavCommand::user(MyActions::Delete), ) }, }); app.init_state::(); app.add_systems(Update, ui_system.run_if(in_state(MenuState::Main))); app.add_systems( Update, empty_state_system.run_if(in_state(MenuState::Empty1)), ); app.add_systems( Update, empty_state_system.run_if(in_state(MenuState::Empty2)), ); app.run(); } fn menu_controls( ui: &mut egui::Ui, state: &State, next_state_setter: &mut NextState, ) { ui.label("Navigation: arrow keys, WASD, gamepad's d-pad, gamepad's left stick."); ui.label("Primary action: left-click, Enter, Space, gamepad's south button."); ui.label("Secondary action: right-click, Delete key, gamepad's north button."); ui.label("Change menu: page up/down, gamepad's upper triggers, these buttons here:"); ui.horizontal(|ui| { let prev_state = match state.get() { MenuState::Main => MenuState::Empty2, MenuState::Empty1 => MenuState::Main, MenuState::Empty2 => MenuState::Empty1, }; let next_state = match state.get() { MenuState::Main => MenuState::Empty1, MenuState::Empty1 => MenuState::Empty2, MenuState::Empty2 => MenuState::Main, }; if ui .button(format!("<<{:?}<<", prev_state)) .kbgp_navigation() .kbgp_focus_label(MyFocusLabel::PrevMenu) .clicked() || ui.kbgp_user_action() == Some(MyActions::PrevMenu) { next_state_setter.set(prev_state); ui.kbgp_clear_input(); ui.kbgp_set_focus_label(MyFocusLabel::PrevMenu); } ui.label(format!("{:?}", state.get())); if ui .button(format!(">>{:?}>>", next_state)) .kbgp_navigation() .kbgp_initial_focus() .kbgp_focus_label(MyFocusLabel::NextMenu) .clicked() || ui.kbgp_user_action() == Some(MyActions::NextMenu) { next_state_setter.set(next_state); ui.ctx().kbgp_clear_input(); ui.ctx().kbgp_set_focus_label(MyFocusLabel::NextMenu); } }); } #[allow(clippy::too_many_arguments)] fn ui_system( mut egui_context: EguiContexts, state: Res>, mut next_state: ResMut>, mut button_counters: Local<[usize; 4]>, mut counter_on_release: Local, mut checkbox_value: Local, mut label_value: Local, mut settable_inputs: Local>>, mut settable_chords: Local>>, mut settable_same_source_chords: Local>>, gamepads: Res, mut settable_inputs_of_source: Local>)>>, mut settable_chords_of_source: Local>)>>, ) { if settable_inputs.is_empty() { for _ in 0..3 { settable_inputs.push(None); } } for chords in [&mut *settable_chords, &mut *settable_same_source_chords] { if chords.is_empty() { for _ in 0..3 { chords.push(Default::default()); } } } let mut all_input_sources = vec![KbgpInputSource::KeyboardAndMouse]; all_input_sources.extend(gamepads.iter().map(KbgpInputSource::Gamepad)); while settable_inputs_of_source.len() < all_input_sources.len() { let source = all_input_sources[settable_inputs_of_source.len()]; settable_inputs_of_source.push((source, vec![None; 3])); } while settable_chords_of_source.len() < all_input_sources.len() { let source = all_input_sources[settable_chords_of_source.len()]; settable_chords_of_source.push((source, vec![Default::default(); 3])); } egui::CentralPanel::default().show(egui_context.ctx_mut(), |ui| { menu_controls(ui, &state, &mut next_state); ui.horizontal(|ui| { for counter in button_counters.iter_mut() { match ui .button(format!("Counter: {counter}")) .kbgp_navigation() .kbgp_activated() { KbgpNavActivation::Clicked => { *counter += 1; } KbgpNavActivation::ClickedSecondary | KbgpNavActivation::User(MyActions::Delete) => { if 0 < *counter { *counter -= 1; } } _ => {} } } }); match ui .button(format!( "Counter (activates on key release): {}", *counter_on_release )) .kbgp_navigation() .kbgp_activate_released() { KbgpNavActivation::Clicked => { *counter_on_release += 1; } KbgpNavActivation::ClickedSecondary | KbgpNavActivation::User(MyActions::Delete) => { if 0 < *counter_on_release { *counter_on_release -= 1; } } _ => {} } if ui .checkbox(&mut checkbox_value.clone(), "Checkbox") .kbgp_navigation() .clicked() { *checkbox_value = !*checkbox_value; } ui.horizontal(|ui| { for i in 0..4 { if ui .selectable_label(*label_value == i, format!("Value {i}")) .kbgp_navigation() .clicked() { *label_value = i; } } }); fn check_for_delete_action(button: &egui::Response) -> bool { matches!( button.kbgp_activated(), KbgpNavActivation::ClickedSecondary | KbgpNavActivation::User(MyActions::Delete) ) } ui.horizontal(|ui| { ui.label("Set key:"); for settable_input in settable_inputs.iter_mut() { let text = if let Some(input) = settable_input { format!("{}", input) } else { "N/A".to_owned() }; let button = ui.button(text).kbgp_navigation(); if let Some(input) = button.kbgp_pending_input() { *settable_input = Some(input); } else if check_for_delete_action(&button) { *settable_input = None; } } }); ui.horizontal(|ui| { ui.label("Set chord:"); for settable_chord in settable_chords.iter_mut() { let text = if settable_chord.is_empty() { "N/A".to_owned() } else { KbgpInput::format_chord(settable_chord.iter().cloned()) }; let button = ui.button(text).kbgp_navigation(); if let Some(chord) = button.kbgp_pending_chord() { *settable_chord = chord; } else if check_for_delete_action(&button) { *settable_chord = Default::default(); } } }); ui.horizontal(|ui| { ui.label("Set chord (same source):"); for settable_chord in settable_same_source_chords.iter_mut() { let text = if settable_chord.is_empty() { "N/A".to_owned() } else { KbgpInput::format_chord(settable_chord.iter().cloned()) }; let button = ui.button(text).kbgp_navigation(); if let Some(chord) = button.kbgp_pending_chord_same_source() { *settable_chord = chord; } else if check_for_delete_action(&button) { *settable_chord = Default::default(); } } }); for (source, settable_inputs) in settable_inputs_of_source.iter_mut() { ui.horizontal(|ui| { ui.label(format!("Set key ({}):", source)); for settable_input in settable_inputs.iter_mut() { let text = if let Some(input) = settable_input { format!("{}", input) } else { "N/A".to_owned() }; let button = ui.button(text).kbgp_navigation(); if let Some(input) = button.kbgp_pending_input_of_source(*source) { *settable_input = Some(input); } else if check_for_delete_action(&button) { *settable_input = None; } } }); } for (source, settable_chords) in settable_chords_of_source.iter_mut() { ui.horizontal(|ui| { ui.label(format!("Set chord ({}):", source)); for settable_chord in settable_chords.iter_mut() { let text = if settable_chord.is_empty() { "N/A".to_owned() } else { KbgpInput::format_chord(settable_chord.iter().cloned()) }; let button = ui.button(text).kbgp_navigation(); if let Some(chord) = button.kbgp_pending_chord_of_source(*source) { *settable_chord = chord; } else if check_for_delete_action(&button) { *settable_chord = Default::default(); } } }); } ui.horizontal(|ui| { #[derive(PartialEq)] enum FocusLabel { Left, Right, } if ui .button("Focus >") .kbgp_navigation() .kbgp_focus_label(FocusLabel::Left) .clicked() { ui.kbgp_set_focus_label(FocusLabel::Right); } if ui .button("< Focus") .kbgp_navigation() .kbgp_focus_label(FocusLabel::Right) .clicked() { ui.kbgp_set_focus_label(FocusLabel::Left); } }); }); } fn empty_state_system( mut egui_context: EguiContexts, state: Res>, mut next_state: ResMut>, ) { egui::CentralPanel::default().show(egui_context.ctx_mut(), |ui| { menu_controls(ui, &state, &mut next_state); }); }