diff --git a/crates/client/src/debug.rs b/crates/client/src/debug.rs index 78141f9..722de16 100644 --- a/crates/client/src/debug.rs +++ b/crates/client/src/debug.rs @@ -4,7 +4,12 @@ //! //! Debug affordances are bound behind a modifier chord so they cannot collide with movement keys: [`DEBUG_MODIFIER`] (F1) is held, and a second key selects the affordance. The currently bound chords are: //! -//! - **F1 + V**: toggles the [`RenderMode::Points`] debug rasterisation mode, which draws one point per mesh vertex. Pressing it again returns to [`RenderMode::Filled`]. +//! - **F1 + V**: filled terrain with vertex points overlaid, showing where the mesher placed geometry without losing the surface. +//! - **F1 + B**: filled terrain with the triangle edges overlaid, showing the size and shape of the emitted quads. +//! +//! Holding a [`SOLO_MODIFIER`] (either Shift) as well drops the filled pass, leaving the debug geometry alone against the clear colour: **F1 + Shift + V** for points only, **F1 + Shift + B** for wireframe only. +//! +//! Each chord toggles: pressing the chord for the active mode returns to [`RenderMode::Filled`]. use renderer::RenderMode; use winit::keyboard::KeyCode; @@ -12,6 +17,9 @@ use winit::keyboard::KeyCode; /// The key that must be held for a debug chord to be recognised. const DEBUG_MODIFIER: KeyCode = KeyCode::F1; +/// The keys that, held alongside [`DEBUG_MODIFIER`], select the solo form of a debug view. Both shifts are accepted so the chord is reachable with either hand. +const SOLO_MODIFIER: [KeyCode; 2] = [KeyCode::ShiftLeft, KeyCode::ShiftRight]; + /// A debug operation requested by the input layer, applied by the caller. /// /// The layer deliberately returns an intent rather than acting directly, so it owns no renderer or window handles and stays a pure function of key events. @@ -26,6 +34,8 @@ pub(crate) enum DebugAction { pub(crate) struct DebugControls { /// Whether [`DEBUG_MODIFIER`] is currently held. Chords are recognised only while this is set. modifier_held: bool, + /// Whether a [`SOLO_MODIFIER`] is currently held, selecting the solo form of the chord. + solo_held: bool, /// The rasterisation mode most recently requested, used to make each chord a toggle back to [`RenderMode::Filled`]. render_mode: RenderMode, } @@ -40,11 +50,17 @@ impl DebugControls { return None; } + // A solo modifier is tracked unconditionally rather than only while the debug modifier is held, so its state is correct whichever of the two is pressed first. + if SOLO_MODIFIER.contains(&code) { + self.solo_held = pressed; + return None; + } + if !pressed || !self.modifier_held { return None; } - let requested = render_mode_for_key(code)?; + let requested = render_mode_for_key(code, self.solo_held)?; // Re-pressing the chord for the active mode returns to the normal path, so a single chord both enables and disables its mode. self.render_mode = if self.render_mode == requested { @@ -57,12 +73,15 @@ impl DebugControls { } } -/// Maps a chord key to the render mode it selects, or [`None`] if the key is unbound. +/// Maps a chord key, and whether a [`SOLO_MODIFIER`] is held, to the render mode it selects. Returns [`None`] if the key is unbound. /// -/// This is the single table a new rasterisation debug mode is added to. -const fn render_mode_for_key(code: KeyCode) -> Option { - match code { - KeyCode::KeyV => Some(RenderMode::Points), +/// This is the single table a new rasterisation debug mode is added to: one key, one overlaid form, one solo form. +const fn render_mode_for_key(code: KeyCode, solo: bool) -> Option { + match (code, solo) { + (KeyCode::KeyV, false) => Some(RenderMode::FilledPoints), + (KeyCode::KeyV, true) => Some(RenderMode::Points), + (KeyCode::KeyB, false) => Some(RenderMode::FilledWireframe), + (KeyCode::KeyB, true) => Some(RenderMode::Wireframe), _ => None, } } diff --git a/crates/client/src/tests/debug.rs b/crates/client/src/tests/debug.rs index eeee158..e02b0fc 100644 --- a/crates/client/src/tests/debug.rs +++ b/crates/client/src/tests/debug.rs @@ -28,12 +28,79 @@ fn modifier_alone_produces_no_action() { fn held_modifier_plus_bound_key_selects_the_mode() { let mut controls = DebugControls::default(); controls.handle_key(DEBUG_MODIFIER, true); + assert_eq!( + tap(&mut controls, KeyCode::KeyV), + Some(DebugAction::SetRenderMode(RenderMode::FilledPoints)) + ); +} + +#[test] +fn each_bound_key_selects_a_distinct_overlay_mode() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + for (code, expected) in [ + (KeyCode::KeyV, RenderMode::FilledPoints), + (KeyCode::KeyB, RenderMode::FilledWireframe), + ] { + assert_eq!( + tap(&mut controls, code), + Some(DebugAction::SetRenderMode(expected)) + ); + } +} + +#[test] +fn solo_modifier_drops_the_filled_pass() { + for solo in SOLO_MODIFIER { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + controls.handle_key(solo, true); + assert_eq!( + tap(&mut controls, KeyCode::KeyV), + Some(DebugAction::SetRenderMode(RenderMode::Points)) + ); + assert_eq!( + tap(&mut controls, KeyCode::KeyB), + Some(DebugAction::SetRenderMode(RenderMode::Wireframe)) + ); + } +} + +#[test] +fn solo_modifier_is_tracked_before_the_debug_modifier() { + let mut controls = DebugControls::default(); + controls.handle_key(KeyCode::ShiftLeft, true); + controls.handle_key(DEBUG_MODIFIER, true); assert_eq!( tap(&mut controls, KeyCode::KeyV), Some(DebugAction::SetRenderMode(RenderMode::Points)) ); } +#[test] +fn releasing_the_solo_modifier_restores_the_overlay_form() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + controls.handle_key(KeyCode::ShiftLeft, true); + tap(&mut controls, KeyCode::KeyV); + controls.handle_key(KeyCode::ShiftLeft, false); + assert_eq!( + tap(&mut controls, KeyCode::KeyV), + Some(DebugAction::SetRenderMode(RenderMode::FilledPoints)) + ); +} + +#[test] +fn switching_between_modes_does_not_pass_through_filled() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + tap(&mut controls, KeyCode::KeyV); + assert_eq!( + tap(&mut controls, KeyCode::KeyB), + Some(DebugAction::SetRenderMode(RenderMode::FilledWireframe)) + ); +} + #[test] fn repeating_the_chord_toggles_back_to_filled() { let mut controls = DebugControls::default(); @@ -67,3 +134,10 @@ fn unbound_key_under_the_modifier_is_ignored() { controls.handle_key(DEBUG_MODIFIER, true); assert_eq!(tap(&mut controls, KeyCode::KeyW), None); } + +#[test] +fn solo_modifier_alone_produces_no_action() { + let mut controls = DebugControls::default(); + assert_eq!(controls.handle_key(KeyCode::ShiftLeft, true), None); + assert_eq!(tap(&mut controls, KeyCode::KeyV), None); +}