Skip to main content

blockworx/
keys.rs

1//! The keyboard's half of the command registry: reading a bound chord off the
2//! frame's input.
3//!
4//! The table itself is [`blockworx_tools::commands::BINDINGS`], over a
5//! vocabulary that names no toolkit; this is where a [`Chord`] becomes an
6//! `egui::KeyboardShortcut` and the frame is asked whether it was pressed.
7
8use blockworx_paint::Chord;
9use blockworx_tools::commands::{BINDINGS, CommandId};
10
11use crate::canvas::convert::IntoEgui as _;
12
13/// Consume any bound chord from the frame's input, returning its command. A
14/// reserved chord is consumed even when its command is currently unavailable
15/// (a locked block's Add Port), so it can't leak into other handlers.
16pub fn consume_binding(ctx: &egui::Context) -> Option<CommandId> {
17    BINDINGS
18        .iter()
19        .find(|(chord, _)| ctx.input_mut(|i| i.consume_shortcut(&chord.egui())))
20        .map(|(_, id)| *id)
21}
22
23/// How `chord` is written on this platform — ⌘ on mac, Ctrl elsewhere.
24pub fn spelled(ctx: &egui::Context, chord: Chord) -> String {
25    ctx.format_shortcut(&chord.egui())
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use blockworx_tools::names::ToolName;
32
33    #[test]
34    fn bound_chords_consume_from_the_input() {
35        let ctx = egui::Context::default();
36        let press = |key| egui::RawInput {
37            events: vec![egui::Event::Key {
38                key,
39                physical_key: None,
40                pressed: true,
41                repeat: false,
42                modifiers: egui::Modifiers::COMMAND,
43            }],
44            ..Default::default()
45        };
46        ctx.run_ui(press(egui::Key::B), |ui| {
47            assert_eq!(
48                consume_binding(ui.ctx()),
49                Some(CommandId::Arm(ToolName::NewBlock))
50            );
51            // Consumed: a second scan finds nothing.
52            assert_eq!(consume_binding(ui.ctx()), None);
53        })
54        .drop_without_applying_deltas();
55        ctx.run_ui(press(egui::Key::Z), |ui| {
56            assert_eq!(consume_binding(ui.ctx()), Some(CommandId::Undo));
57        })
58        .drop_without_applying_deltas();
59    }
60}