From 8c11a0144608c569261790c74abbe3918921ec06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Mon, 14 Sep 2026 14:33:04 +0200 Subject: [PATCH 1/2] Make debug menu usable with mouse and keyboard --- src/debug_tools.rs | 378 +++++++++++++++++++++++++++++++-------------- 1 file changed, 263 insertions(+), 115 deletions(-) diff --git a/src/debug_tools.rs b/src/debug_tools.rs index 82bc762..d3794f0 100644 --- a/src/debug_tools.rs +++ b/src/debug_tools.rs @@ -48,7 +48,7 @@ impl PresentationMotionMode { } } -#[derive(EnumCycle, Reflect)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumCycle, Reflect)] pub enum GameSpeedDebug { Full, Half, @@ -387,6 +387,84 @@ fn page_len(page: DebugMenuPage, player_handles: &[usize]) -> usize { } } +fn queue_settings_save(commands: &mut Commands) { + commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1))); +} + +fn activate_selected( + settings: &mut DebugSettings, + page_len: usize, + player_handles: &[usize], + commands: &mut Commands, +) { + if page_len == 0 { + return; + } + + match settings.page() { + DebugMenuPage::Static(entries) => match entries[settings.selected()] { + DebugMenuEntry::StopRecording { .. } => { + commands.insert_resource(StopReplayRecording); + } + DebugMenuEntry::Toggle { flag, .. } => { + settings.toggle_flag(flag); + queue_settings_save(commands); + } + DebugMenuEntry::Submenu { entries, .. } => { + settings.push_page(DebugMenuPage::Static(entries)); + } + DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList), + DebugMenuEntry::MotionSampling { .. } => { + settings.cycle_motion_sampling(); + queue_settings_save(commands); + } + DebugMenuEntry::GameSpeed { .. } => { + commands.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate())); + queue_settings_save(commands); + } + }, + DebugMenuPage::PlayerList => { + let handle = player_handles[settings.selected()]; + settings.rows_mut(handle); + settings.push_page(DebugMenuPage::PlayerRows(handle)); + } + DebugMenuPage::PlayerRows(_) => {} + } +} + +fn cycle_selected_value(settings: &mut DebugSettings, direction: i8, commands: &mut Commands) { + match settings.page() { + DebugMenuPage::Static(entries) => { + if let Some(entry) = entries.get(settings.selected()) { + match entry { + DebugMenuEntry::MotionSampling { .. } => { + settings.cycle_motion_sampling(); + queue_settings_save(commands); + } + DebugMenuEntry::GameSpeed { .. } => { + settings.game_speed = if direction > 0 { + settings.game_speed.up() + } else { + settings.game_speed.down() + }; + commands + .insert_resource(RollbackFrameRate(settings.game_speed.frame_rate())); + queue_settings_save(commands); + } + _ => {} + } + } + } + DebugMenuPage::PlayerRows(handle) => { + let selected = settings.selected(); + let rows = settings.rows_mut(handle); + rows[selected] = rows[selected].cycle(direction); + queue_settings_save(commands); + } + DebugMenuPage::PlayerList => {} + } +} + fn handle_palette_input( keyboard: Res>, gamepads: Query<(Entity, &Gamepad)>, @@ -398,6 +476,15 @@ fn handle_palette_input( settings.toggle_flag(DebugFlag::DrawThroughGeometry); } + if keyboard.just_pressed(KeyCode::F3) { + settings.menu_open = !settings.menu_open; + settings.controller = None; + if settings.menu_open { + settings.reset_navigation(); + } + return; + } + if !settings.menu_open { for (entity, gamepad) in &gamepads { if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp) @@ -411,14 +498,15 @@ fn handle_palette_input( return; } - let Some(controller) = settings.controller else { - settings.menu_open = false; - return; - }; - let Ok((_, gamepad)) = gamepads.get(controller) else { - settings.menu_open = false; - settings.controller = None; - return; + let gamepad = if let Some(controller) = settings.controller { + let Ok((_, gamepad)) = gamepads.get(controller) else { + settings.menu_open = false; + settings.controller = None; + return; + }; + Some(gamepad) + } else { + None }; let player_handles = player_handles(&players); let page = settings.page(); @@ -428,95 +516,51 @@ fn handle_palette_input( *settings.selected_mut() = 0; } - if gamepad.just_pressed(GamepadButton::DPadUp) && page_len != 0 { + if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadUp)) + || keyboard.just_pressed(KeyCode::ArrowUp) + || keyboard.just_pressed(KeyCode::KeyW)) + && page_len != 0 + { let selected = settings.selected_mut(); *selected = selected.checked_sub(1).unwrap_or(page_len - 1); } - if gamepad.just_pressed(GamepadButton::DPadDown) && page_len != 0 { + if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadDown)) + || keyboard.just_pressed(KeyCode::ArrowDown) + || keyboard.just_pressed(KeyCode::KeyS)) + && page_len != 0 + { let selected = settings.selected_mut(); *selected = (*selected + 1) % page_len; } - let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) { + let direction = if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadLeft)) + || keyboard.just_pressed(KeyCode::ArrowLeft) + || keyboard.just_pressed(KeyCode::KeyA) + { Some(-1) - } else if gamepad.just_pressed(GamepadButton::DPadRight) { + } else if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadRight)) + || keyboard.just_pressed(KeyCode::ArrowRight) + || keyboard.just_pressed(KeyCode::KeyD) + { Some(1) } else { None }; if let Some(direction) = direction { - match page { - DebugMenuPage::Static(entries) => { - if let Some(entry) = entries.get(settings.selected()) { - match entry { - DebugMenuEntry::StopRecording { .. } => {} - DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(), - DebugMenuEntry::GameSpeed { .. } => { - if direction > 0 { - settings.game_speed = settings.game_speed.up(); - } else { - settings.game_speed = settings.game_speed.down(); - } - - commands.insert_resource(RollbackFrameRate( - settings.game_speed.frame_rate(), - )); - } - _ => {} - } - } - } - DebugMenuPage::PlayerList => todo!(), - DebugMenuPage::PlayerRows(_) => { - let DebugMenuPage::PlayerRows(handle) = page else { - unreachable!("player row navigation must have a player row page") - }; - let selected = settings.selected(); - let rows = settings.rows_mut(handle); - rows[selected] = rows[selected].cycle(direction); - - commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1))); - } - } + cycle_selected_value(&mut settings, direction, &mut commands); } - - if matches!(page, DebugMenuPage::PlayerRows(_)) { - let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) { - Some(-1) - } else if gamepad.just_pressed(GamepadButton::DPadRight) { - Some(1) - } else { - None - }; - if let Some(_direction) = direction {} + if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::South)) + || keyboard.just_pressed(KeyCode::Enter) + || keyboard.just_pressed(KeyCode::Space)) + && page_len != 0 + { + activate_selected(&mut settings, page_len, &player_handles, &mut commands); } - if gamepad.just_pressed(GamepadButton::South) { - match page { - DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] { - DebugMenuEntry::StopRecording { .. } => { - commands.insert_resource(StopReplayRecording); - } - DebugMenuEntry::Toggle { flag, .. } => { - settings.toggle_flag(flag); - commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1))); - } - DebugMenuEntry::Submenu { entries, .. } => { - settings.push_page(DebugMenuPage::Static(entries)); - } - DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList), - DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(), - DebugMenuEntry::GameSpeed { .. } => {} - }, - DebugMenuPage::PlayerList if page_len != 0 => { - let handle = player_handles[settings.selected()]; - settings.rows_mut(handle); - settings.push_page(DebugMenuPage::PlayerRows(handle)); - } - _ => {} - } - } - if gamepad.just_pressed(GamepadButton::West) && !settings.pop_page() { + if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::West)) + || keyboard.just_pressed(KeyCode::Escape)) + && !settings.pop_page() + { settings.menu_open = false; settings.controller = None; } @@ -538,8 +582,9 @@ fn sync_gizmo_depth_bias(settings: Res, mut configs: ResMut, + mut settings: ResMut, players: Query<&Player>, + mut commands: Commands, ) -> Result { if !settings.menu_open { return Ok(()); @@ -547,7 +592,7 @@ fn draw_palette( let player_handles = player_handles(&players); let page = settings.page(); - let selected = settings.selected(); + let page_len = page_len(page, &player_handles); egui::Window::new("Debug") .anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0)) .resizable(false) @@ -556,38 +601,111 @@ fn draw_palette( match page { DebugMenuPage::Static(entries) => { for (index, entry) in entries.iter().enumerate() { - let marker = if index == selected { ">" } else { " " }; - match entry { - DebugMenuEntry::StopRecording { label } => { - ui.label(format!("{marker} {label}")); + ui.horizontal(|ui| { + ui.label(if index == settings.selected() { + ">" + } else { + " " + }); + match entry { + DebugMenuEntry::StopRecording { label } => { + if ui.button(*label).clicked() { + *settings.selected_mut() = index; + activate_selected( + &mut settings, + page_len, + &player_handles, + &mut commands, + ); + } + } + DebugMenuEntry::Toggle { label, flag } => { + let mut value = settings.flag(*flag); + if ui.checkbox(&mut value, *label).changed() { + settings.toggle_flag(*flag); + queue_settings_save(&mut commands); + } + } + DebugMenuEntry::Submenu { label, .. } + | DebugMenuEntry::PlayerSlots { label } => { + if ui.button(format!("{label} >")).clicked() { + *settings.selected_mut() = index; + activate_selected( + &mut settings, + page_len, + &player_handles, + &mut commands, + ); + } + } + DebugMenuEntry::MotionSampling { label } => { + let old = settings.motion_sampling; + egui::ComboBox::from_id_salt(("debug-motion", index)) + .width(180.0) + .selected_text(format!("{label}: {}", old.label())) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut settings.motion_sampling, + PresentationMotionMode::Interpolation, + "Interpolation", + ); + ui.selectable_value( + &mut settings.motion_sampling, + PresentationMotionMode::Extrapolation, + "Extrapolation", + ); + }); + if settings.motion_sampling != old { + queue_settings_save(&mut commands); + } + } + DebugMenuEntry::GameSpeed { label } => { + let old = settings.game_speed; + egui::ComboBox::from_id_salt(("debug-speed", index)) + .width(180.0) + .selected_text(format!("{label}: {}", old.label())) + .show_ui(ui, |ui| { + for speed in [ + GameSpeedDebug::Full, + GameSpeedDebug::Half, + GameSpeedDebug::Quarter, + ] { + ui.selectable_value( + &mut settings.game_speed, + speed, + speed.label(), + ); + } + }); + if settings.game_speed != old { + commands.insert_resource(RollbackFrameRate( + settings.game_speed.frame_rate(), + )); + queue_settings_save(&mut commands); + } + } } - DebugMenuEntry::Toggle { label, flag } => { - let state = if settings.flag(*flag) { "on" } else { "off" }; - ui.label(format!("{marker} {label}: {state}")); - } - DebugMenuEntry::Submenu { label, .. } - | DebugMenuEntry::PlayerSlots { label } => { - ui.label(format!("{marker} {label} >")); - } - DebugMenuEntry::MotionSampling { label } => { - ui.label(format!( - "{marker} {label}: {}", - settings.motion_sampling.label() - )); - } - DebugMenuEntry::GameSpeed { label } => { - ui.label(format!( - "{marker} {label}: {}", - settings.game_speed.label() - )); - } - } + }); } } DebugMenuPage::PlayerList => { for (index, handle) in player_handles.iter().enumerate() { - let marker = if index == selected { ">" } else { " " }; - ui.label(format!("{marker} Player {} >", handle + 1)); + ui.horizontal(|ui| { + ui.label(if index == settings.selected() { + ">" + } else { + " " + }); + if ui.button(format!("Player {} >", handle + 1)).clicked() { + *settings.selected_mut() = index; + activate_selected( + &mut settings, + page_len, + &player_handles, + &mut commands, + ); + } + }); } if player_handles.is_empty() { ui.label("No players in match"); @@ -600,16 +718,30 @@ fn draw_palette( .unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]); ui.label(format!("Player {}", handle + 1)); for (index, row) in rows.iter().enumerate() { - let marker = if index == selected { ">" } else { " " }; - ui.label(format!("{marker} Row {}: {}", index + 1, row.label())); + ui.horizontal(|ui| { + ui.label(if index == settings.selected() { + ">" + } else { + " " + }); + egui::ComboBox::from_id_salt(("debug-row", handle, index)) + .width(180.0) + .selected_text(format!("Row {}: {}", index + 1, row.label())) + .show_ui(ui, |ui| { + let rows = settings.rows_mut(handle); + for kind in FighterDebugRowKind::ALL { + ui.selectable_value(&mut rows[index], kind, kind.label()); + } + }); + }); } } } ui.separator(); let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) { - "D-pad: select/change B: back" + "Mouse: choose Arrows/WASD: select/change Escape/B: back" } else { - "D-pad: select A: enter/toggle Left/right: change B: back" + "Mouse: activate Arrows/WASD: select Enter/A: activate Escape/B: back" }; ui.label(controls); }); @@ -699,4 +831,20 @@ mod tests { mode.cycle(); assert_eq!(mode, PresentationMotionMode::Interpolation); } + + #[test] + fn game_speed_has_expected_frame_rates() { + assert_eq!(GameSpeedDebug::Full.frame_rate(), 60); + assert_eq!(GameSpeedDebug::Half.frame_rate(), 30); + assert_eq!(GameSpeedDebug::Quarter.frame_rate(), 15); + } + + #[test] + fn empty_player_list_does_not_activate() { + let mut settings = DebugSettings::default(); + settings.push_page(DebugMenuPage::PlayerList); + + assert_eq!(page_len(settings.page(), &[]), 0); + assert_eq!(settings.selected(), 0); + } } From 868df95cd4bc9c9e77a3cd789199337eceba1b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Mon, 14 Sep 2026 15:05:21 +0200 Subject: [PATCH 2/2] wording fixes --- src/debug_tools.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/debug_tools.rs b/src/debug_tools.rs index d3794f0..9ad23db 100644 --- a/src/debug_tools.rs +++ b/src/debug_tools.rs @@ -739,9 +739,9 @@ fn draw_palette( } ui.separator(); let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) { - "Mouse: choose Arrows/WASD: select/change Escape/B: back" + "Arrows/WASD: select/change Escape/B: back" } else { - "Mouse: activate Arrows/WASD: select Enter/A: activate Escape/B: back" + "Arrows/WASD: select Enter/A: activate Escape/B: back" }; ui.label(controls); });