112 lines
5.2 KiB
Rust
112 lines
5.2 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
//! Debug-only input handling, kept separate from the gameplay input path.
|
|
//!
|
|
//! 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**: 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.
|
|
//! - **F1 + I**: the debug statistics panel.
|
|
//!
|
|
//! 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;
|
|
|
|
/// 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];
|
|
|
|
/// The key that, held alongside [`DEBUG_MODIFIER`], selects the vertex-point view.
|
|
const VERTEX_POINTS_OVERLAY_KEY: KeyCode = KeyCode::KeyV;
|
|
|
|
/// The key that, held alongside [`DEBUG_MODIFIER`], selects the wireframe view.
|
|
const WIREFRAME_OVERLAY_KEY: KeyCode = KeyCode::KeyB;
|
|
|
|
/// The key that, held alongside [`DEBUG_MODIFIER`], toggles the statistics panel.
|
|
const STATS_KEY: KeyCode = KeyCode::KeyI;
|
|
|
|
/// 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.
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum DebugAction {
|
|
/// Applies the given rasterisation mode to the renderer.
|
|
SetRenderMode(RenderMode),
|
|
/// Enables or disables emission of the statistics panel.
|
|
SetStatsOverlay(bool),
|
|
}
|
|
|
|
/// Owns debug-only input state and translates key events into [`DebugAction`]s.
|
|
#[derive(Default)]
|
|
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,
|
|
/// Whether the statistics panel is being emitted.
|
|
stats_enabled: bool,
|
|
}
|
|
|
|
impl DebugControls {
|
|
/// Translates one key event into a debug action, updating internal state.
|
|
///
|
|
/// Returns [`None`] when the event is not part of a debug chord, which is the common case; the caller then handles the key normally. Actions fire on the press edge only, so one physical tap toggles once rather than once per press and once per release.
|
|
pub(crate) fn handle_key(&mut self, code: KeyCode, pressed: bool) -> Option<DebugAction> {
|
|
if code == DEBUG_MODIFIER {
|
|
self.modifier_held = pressed;
|
|
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;
|
|
}
|
|
|
|
// The statistics chord is resolved before the raster table so the two axes never contend for a key. The solo modifier selects between overlaid and standalone geometry and has no meaning for a panel that draws none, so it is ignored here.
|
|
if code == STATS_KEY {
|
|
self.stats_enabled = !self.stats_enabled;
|
|
return Some(DebugAction::SetStatsOverlay(self.stats_enabled));
|
|
}
|
|
|
|
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 {
|
|
RenderMode::Filled
|
|
} else {
|
|
requested
|
|
};
|
|
|
|
Some(DebugAction::SetRenderMode(self.render_mode))
|
|
}
|
|
}
|
|
|
|
/// 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: one key, one overlaid form, one solo form.
|
|
const fn render_mode_for_key(code: KeyCode, solo: bool) -> Option<RenderMode> {
|
|
match (code, solo) {
|
|
(VERTEX_POINTS_OVERLAY_KEY, false) => Some(RenderMode::FilledPoints),
|
|
(VERTEX_POINTS_OVERLAY_KEY, true) => Some(RenderMode::Points),
|
|
(WIREFRAME_OVERLAY_KEY, false) => Some(RenderMode::FilledWireframe),
|
|
(WIREFRAME_OVERLAY_KEY, true) => Some(RenderMode::Wireframe),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "tests/debug.rs"]
|
|
mod tests;
|