feat(client): add a solo modifier for debug view chords
This commit is contained in:
parent
324d1b144d
commit
1d041e035e
|
|
@ -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<RenderMode> {
|
||||
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<RenderMode> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue