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:
|
//! 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 renderer::RenderMode;
|
||||||
use winit::keyboard::KeyCode;
|
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.
|
/// The key that must be held for a debug chord to be recognised.
|
||||||
const DEBUG_MODIFIER: KeyCode = KeyCode::F1;
|
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.
|
/// 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.
|
/// 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 {
|
pub(crate) struct DebugControls {
|
||||||
/// Whether [`DEBUG_MODIFIER`] is currently held. Chords are recognised only while this is set.
|
/// Whether [`DEBUG_MODIFIER`] is currently held. Chords are recognised only while this is set.
|
||||||
modifier_held: bool,
|
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`].
|
/// The rasterisation mode most recently requested, used to make each chord a toggle back to [`RenderMode::Filled`].
|
||||||
render_mode: RenderMode,
|
render_mode: RenderMode,
|
||||||
}
|
}
|
||||||
|
|
@ -40,11 +50,17 @@ impl DebugControls {
|
||||||
return None;
|
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 {
|
if !pressed || !self.modifier_held {
|
||||||
return None;
|
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.
|
// 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 {
|
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.
|
/// 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) -> Option<RenderMode> {
|
const fn render_mode_for_key(code: KeyCode, solo: bool) -> Option<RenderMode> {
|
||||||
match code {
|
match (code, solo) {
|
||||||
KeyCode::KeyV => Some(RenderMode::Points),
|
(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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,79 @@ fn modifier_alone_produces_no_action() {
|
||||||
fn held_modifier_plus_bound_key_selects_the_mode() {
|
fn held_modifier_plus_bound_key_selects_the_mode() {
|
||||||
let mut controls = DebugControls::default();
|
let mut controls = DebugControls::default();
|
||||||
controls.handle_key(DEBUG_MODIFIER, true);
|
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!(
|
assert_eq!(
|
||||||
tap(&mut controls, KeyCode::KeyV),
|
tap(&mut controls, KeyCode::KeyV),
|
||||||
Some(DebugAction::SetRenderMode(RenderMode::Points))
|
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]
|
#[test]
|
||||||
fn repeating_the_chord_toggles_back_to_filled() {
|
fn repeating_the_chord_toggles_back_to_filled() {
|
||||||
let mut controls = DebugControls::default();
|
let mut controls = DebugControls::default();
|
||||||
|
|
@ -67,3 +134,10 @@ fn unbound_key_under_the_modifier_is_ignored() {
|
||||||
controls.handle_key(DEBUG_MODIFIER, true);
|
controls.handle_key(DEBUG_MODIFIER, true);
|
||||||
assert_eq!(tap(&mut controls, KeyCode::KeyW), None);
|
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