Skip to main content

dais_ui/
test_input.rs

1//! Test-input diagnostic mode.
2//!
3//! A minimal `eframe::App` that displays key events and their mapped actions,
4//! useful for debugging clicker/remote hardware setups.
5
6use std::collections::VecDeque;
7
8use dais_core::keybindings::{KeyCombo, KeybindingMap};
9
10/// Maximum number of key events to display.
11const MAX_EVENTS: usize = 20;
12
13/// A recorded key event with its resolved action (if any).
14struct KeyEvent {
15    key_name: String,
16    modifiers: String,
17    action: String,
18}
19
20/// Standalone eframe app for the `--test-input` diagnostic mode.
21pub struct TestInputApp {
22    keybindings: KeybindingMap,
23    events: VecDeque<KeyEvent>,
24}
25
26impl TestInputApp {
27    pub fn new(keybindings: KeybindingMap) -> Self {
28        Self { keybindings, events: VecDeque::with_capacity(MAX_EVENTS + 1) }
29    }
30}
31
32impl eframe::App for TestInputApp {
33    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
34        // Collect key events this frame
35        let raw_events: Vec<egui::Event> = ctx.input(|i| i.events.clone());
36
37        for event in &raw_events {
38            if let egui::Event::Key { key, pressed: true, modifiers, .. } = event {
39                let key_name = crate::input::egui_key_name_public(*key);
40                let combo = KeyCombo {
41                    key: key_name.clone(),
42                    shift: modifiers.shift,
43                    ctrl: modifiers.ctrl || modifiers.command,
44                    alt: modifiers.alt,
45                };
46
47                let mod_str = format_modifiers(*modifiers);
48                let action = match self.keybindings.lookup(&combo) {
49                    Some(a) => a.config_name().to_string(),
50                    None => "(none)".to_string(),
51                };
52
53                tracing::info!(
54                    key = %key_name,
55                    modifiers = %mod_str,
56                    action = %action,
57                    "Key event captured"
58                );
59
60                self.events.push_front(KeyEvent { key_name, modifiers: mod_str, action });
61                if self.events.len() > MAX_EVENTS {
62                    self.events.pop_back();
63                }
64            }
65        }
66
67        // Check for Escape to exit
68        let wants_exit = ctx.input(|i| i.key_pressed(egui::Key::Escape));
69
70        egui::CentralPanel::default().show(ctx, |ui| {
71            ui.heading("Test Input Mode — Press keys to see events");
72            ui.separator();
73
74            if self.events.is_empty() {
75                ui.label("No key events yet. Press any key on your clicker or keyboard.");
76            } else {
77                egui::ScrollArea::vertical().max_height(ui.available_height() - 40.0).show(
78                    ui,
79                    |ui| {
80                        egui::Grid::new("key_events")
81                            .num_columns(3)
82                            .striped(true)
83                            .spacing([20.0, 4.0])
84                            .show(ui, |ui| {
85                                ui.strong("Key");
86                                ui.strong("Modifiers");
87                                ui.strong("Action");
88                                ui.end_row();
89
90                                for ev in &self.events {
91                                    ui.monospace(&ev.key_name);
92                                    ui.monospace(&ev.modifiers);
93                                    ui.monospace(&ev.action);
94                                    ui.end_row();
95                                }
96                            });
97                    },
98                );
99            }
100
101            ui.separator();
102            if ui.button("Exit").clicked() || wants_exit {
103                ctx.send_viewport_cmd(egui::ViewportCommand::Close);
104            }
105        });
106    }
107}
108
109/// Format modifier flags into a human-readable string.
110fn format_modifiers(m: egui::Modifiers) -> String {
111    let mut parts = Vec::new();
112    if m.ctrl || m.command {
113        parts.push("Ctrl");
114    }
115    if m.shift {
116        parts.push("Shift");
117    }
118    if m.alt {
119        parts.push("Alt");
120    }
121    if parts.is_empty() { "(none)".to_string() } else { parts.join("+") }
122}