1use super::SimpleDebugContext;
4use crate::keybindings::{BindingContext, Keybindings};
5use ratatui::style::{Color, Modifier, Style};
6
7const NEON_PURPLE: Color = Color::Rgb(160, 100, 220);
9const NEON_PINK: Color = Color::Rgb(255, 100, 150);
10const NEON_AMBER: Color = Color::Rgb(255, 191, 0);
11const NEON_CYAN: Color = Color::Rgb(0, 255, 255);
12const NEON_GREEN: Color = Color::Rgb(80, 255, 120);
13const ELECTRIC_BLUE: Color = Color::Rgb(80, 180, 255);
14const KINDA_GREEN: Color = Color::Rgb(40, 220, 80);
15
16const BG_DEEP: Color = Color::Rgb(12, 14, 22);
17const BG_PANEL: Color = Color::Rgb(18, 21, 32);
18const BG_SURFACE: Color = Color::Rgb(26, 30, 44);
19const BG_HIGHLIGHT: Color = Color::Rgb(45, 50, 70);
20
21const TEXT_PRIMARY: Color = Color::Rgb(240, 240, 245);
22const TEXT_SECONDARY: Color = Color::Rgb(150, 150, 160);
23
24#[derive(Debug, Clone)]
26pub struct DebugStyle {
27 pub banner_bg: Style,
29 pub title_style: Style,
31 pub key_styles: KeyStyles,
33 pub label_style: Style,
35 pub value_style: Style,
37 pub dim_factor: f32,
39}
40
41#[derive(Debug, Clone)]
43pub struct KeyStyles {
44 pub toggle: Style,
46 pub state: Style,
48 pub copy: Style,
50 pub mouse: Style,
52 pub actions: Style,
54}
55
56impl Default for KeyStyles {
57 fn default() -> Self {
58 let key_base = |bg: Color| {
59 Style::default()
60 .fg(BG_DEEP)
61 .bg(bg)
62 .add_modifier(Modifier::BOLD)
63 };
64 Self {
65 toggle: key_base(NEON_PINK),
66 state: key_base(NEON_CYAN),
67 copy: key_base(NEON_AMBER),
68 mouse: key_base(ELECTRIC_BLUE),
69 actions: key_base(KINDA_GREEN),
70 }
71 }
72}
73
74impl Default for DebugStyle {
75 fn default() -> Self {
76 Self {
77 banner_bg: Style::default().bg(BG_DEEP),
78 title_style: Style::default()
79 .fg(BG_DEEP)
80 .bg(NEON_PURPLE)
81 .add_modifier(Modifier::BOLD),
82 key_styles: KeyStyles::default(),
83 label_style: Style::default().fg(TEXT_SECONDARY),
84 value_style: Style::default().fg(TEXT_PRIMARY),
85 dim_factor: 0.7,
86 }
87 }
88}
89
90impl DebugStyle {
92 pub const fn neon_purple() -> Color {
94 NEON_PURPLE
95 }
96 pub const fn neon_cyan() -> Color {
98 NEON_CYAN
99 }
100 pub const fn neon_amber() -> Color {
102 NEON_AMBER
103 }
104 pub const fn neon_green() -> Color {
106 NEON_GREEN
107 }
108 pub const fn bg_deep() -> Color {
110 BG_DEEP
111 }
112 pub const fn bg_panel() -> Color {
114 BG_PANEL
115 }
116 pub const fn bg_surface() -> Color {
118 BG_SURFACE
119 }
120 pub const fn bg_highlight() -> Color {
122 BG_HIGHLIGHT
123 }
124 pub const fn text_primary() -> Color {
126 TEXT_PRIMARY
127 }
128 pub const fn text_secondary() -> Color {
130 TEXT_SECONDARY
131 }
132}
133
134#[derive(Debug, Clone)]
136pub struct StatusItem {
137 pub label: String,
139 pub value: String,
141 pub style: Option<Style>,
143}
144
145impl StatusItem {
146 pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
148 Self {
149 label: label.into(),
150 value: value.into(),
151 style: None,
152 }
153 }
154
155 pub fn with_style(mut self, style: Style) -> Self {
157 self.style = Some(style);
158 self
159 }
160}
161
162#[derive(Clone)]
164pub struct DebugConfig<C: BindingContext> {
165 pub keybindings: Keybindings<C>,
167 pub debug_context: C,
169 pub style: DebugStyle,
171 status_provider: Option<StatusProvider>,
173}
174
175type StatusProvider = std::sync::Arc<dyn Fn() -> Vec<StatusItem> + Send + Sync>;
176
177impl<C: BindingContext> std::fmt::Debug for DebugConfig<C> {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 f.debug_struct("DebugConfig")
180 .field("debug_context", &self.debug_context.name())
181 .field("style", &self.style)
182 .field(
183 "status_provider",
184 &self.status_provider.as_ref().map(|_| "<fn>"),
185 )
186 .finish()
187 }
188}
189
190impl<C: BindingContext> DebugConfig<C> {
191 pub fn new(keybindings: Keybindings<C>, debug_context: C) -> Self {
193 Self {
194 keybindings,
195 debug_context,
196 style: DebugStyle::default(),
197 status_provider: None,
198 }
199 }
200
201 pub fn with_style(mut self, style: DebugStyle) -> Self {
203 self.style = style;
204 self
205 }
206
207 pub fn with_status_provider<F>(mut self, provider: F) -> Self
211 where
212 F: Fn() -> Vec<StatusItem> + Send + Sync + 'static,
213 {
214 self.status_provider = Some(std::sync::Arc::new(provider));
215 self
216 }
217
218 pub fn status_items(&self) -> Vec<StatusItem> {
220 self.status_provider
221 .as_ref()
222 .map(|f| f())
223 .unwrap_or_default()
224 }
225}
226
227pub fn default_debug_keybindings() -> Keybindings<SimpleDebugContext> {
247 default_debug_keybindings_with_toggle(&["F12", "Esc"])
248}
249
250pub fn default_debug_keybindings_with_toggle(
267 toggle_keys: &[&str],
268) -> Keybindings<SimpleDebugContext> {
269 let mut kb = Keybindings::new();
270 kb.add(
271 SimpleDebugContext::Debug,
272 "debug.toggle",
273 toggle_keys.iter().map(|s| (*s).into()).collect(),
274 );
275 kb.add(
276 SimpleDebugContext::Debug,
277 "debug.state",
278 vec!["s".into(), "S".into()],
279 );
280 kb.add(
281 SimpleDebugContext::Debug,
282 "debug.copy",
283 vec!["y".into(), "Y".into()],
284 );
285 kb.add(
286 SimpleDebugContext::Debug,
287 "debug.mouse",
288 vec!["i".into(), "I".into()],
289 );
290 kb.add(
291 SimpleDebugContext::Debug,
292 "debug.action_log",
293 vec!["a".into(), "A".into()],
294 );
295 kb
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
304 enum TestContext {
305 Debug,
306 }
307
308 impl BindingContext for TestContext {
309 fn name(&self) -> &'static str {
310 "debug"
311 }
312 fn from_name(name: &str) -> Option<Self> {
313 (name == "debug").then_some(TestContext::Debug)
314 }
315 fn all() -> &'static [Self] {
316 &[TestContext::Debug]
317 }
318 }
319
320 #[test]
321 fn test_status_item() {
322 let item = StatusItem::new("keys", "42");
323 assert_eq!(item.label, "keys");
324 assert_eq!(item.value, "42");
325 assert!(item.style.is_none());
326
327 let styled = item.with_style(Style::default().fg(Color::Red));
328 assert!(styled.style.is_some());
329 }
330
331 #[test]
332 fn test_config_with_status_provider() {
333 let config = DebugConfig::new(Keybindings::new(), TestContext::Debug)
334 .with_status_provider(|| vec![StatusItem::new("test", "value")]);
335
336 let items = config.status_items();
337 assert_eq!(items.len(), 1);
338 assert_eq!(items[0].label, "test");
339 }
340
341 #[test]
342 fn test_config_without_provider() {
343 let config: DebugConfig<TestContext> =
344 DebugConfig::new(Keybindings::new(), TestContext::Debug);
345 let items = config.status_items();
346 assert!(items.is_empty());
347 }
348
349 #[test]
350 fn test_default_debug_keybindings() {
351 let kb = default_debug_keybindings();
352 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
353
354 assert!(bindings.contains_key("debug.toggle"));
356 assert!(bindings.contains_key("debug.state"));
357 assert!(bindings.contains_key("debug.copy"));
358 assert!(bindings.contains_key("debug.mouse"));
359
360 let toggle = bindings.get("debug.toggle").unwrap();
362 assert!(toggle.contains(&"F12".to_string()));
363 assert!(toggle.contains(&"Esc".to_string()));
364
365 let state = bindings.get("debug.state").unwrap();
367 assert!(state.contains(&"s".to_string()));
368 assert!(state.contains(&"S".to_string()));
369
370 let copy = bindings.get("debug.copy").unwrap();
372 assert!(copy.contains(&"y".to_string()));
373 assert!(copy.contains(&"Y".to_string()));
374
375 let mouse = bindings.get("debug.mouse").unwrap();
377 assert!(mouse.contains(&"i".to_string()));
378 assert!(mouse.contains(&"I".to_string()));
379 }
380
381 #[test]
382 fn test_default_debug_keybindings_with_toggle_custom() {
383 let kb = default_debug_keybindings_with_toggle(&["F11"]);
384 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
385
386 let toggle = bindings.get("debug.toggle").unwrap();
388 assert!(toggle.contains(&"F11".to_string()));
389 assert!(!toggle.contains(&"F12".to_string())); let state = bindings.get("debug.state").unwrap();
393 assert!(state.contains(&"s".to_string()));
394 }
395
396 #[test]
397 fn test_default_debug_keybindings_with_toggle_multiple() {
398 let kb = default_debug_keybindings_with_toggle(&["F11", "Ctrl+D"]);
399 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
400
401 let toggle = bindings.get("debug.toggle").unwrap();
402 assert!(toggle.contains(&"F11".to_string()));
403 assert!(toggle.contains(&"Ctrl+D".to_string()));
404 }
405}