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 scrollbar: ScrollbarStyle,
35 pub label_style: Style,
37 pub value_style: Style,
39 pub dim_factor: f32,
41}
42
43#[derive(Debug, Clone, Default)]
45pub struct ScrollbarStyle {
46 pub thumb: Style,
48 pub track: Style,
50 pub begin: Style,
52 pub end: Style,
54 pub thumb_symbol: Option<&'static str>,
56 pub track_symbol: Option<&'static str>,
58 pub begin_symbol: Option<&'static str>,
60 pub end_symbol: Option<&'static str>,
62}
63
64#[derive(Debug, Clone)]
66pub struct KeyStyles {
67 pub toggle: Style,
69 pub state: Style,
71 pub copy: Style,
73 pub mouse: Style,
75 pub actions: Style,
77}
78
79impl Default for KeyStyles {
80 fn default() -> Self {
81 let key_base = |bg: Color| {
82 Style::default()
83 .fg(BG_DEEP)
84 .bg(bg)
85 .add_modifier(Modifier::BOLD)
86 };
87 Self {
88 toggle: key_base(NEON_PINK),
89 state: key_base(NEON_CYAN),
90 copy: key_base(NEON_AMBER),
91 mouse: key_base(ELECTRIC_BLUE),
92 actions: key_base(KINDA_GREEN),
93 }
94 }
95}
96
97impl Default for DebugStyle {
98 fn default() -> Self {
99 Self {
100 banner_bg: Style::default().bg(BG_DEEP),
101 title_style: Style::default()
102 .fg(BG_DEEP)
103 .bg(NEON_PURPLE)
104 .add_modifier(Modifier::BOLD),
105 key_styles: KeyStyles::default(),
106 scrollbar: ScrollbarStyle::default(),
107 label_style: Style::default().fg(TEXT_SECONDARY),
108 value_style: Style::default().fg(TEXT_PRIMARY),
109 dim_factor: 0.7,
110 }
111 }
112}
113
114impl DebugStyle {
116 pub const fn neon_purple() -> Color {
118 NEON_PURPLE
119 }
120 pub const fn neon_cyan() -> Color {
122 NEON_CYAN
123 }
124 pub const fn neon_amber() -> Color {
126 NEON_AMBER
127 }
128 pub const fn neon_green() -> Color {
130 NEON_GREEN
131 }
132 pub const fn bg_deep() -> Color {
134 BG_DEEP
135 }
136 pub const fn bg_panel() -> Color {
138 BG_PANEL
139 }
140 pub const fn bg_surface() -> Color {
142 BG_SURFACE
143 }
144 pub const fn bg_highlight() -> Color {
146 BG_HIGHLIGHT
147 }
148 pub const fn text_primary() -> Color {
150 TEXT_PRIMARY
151 }
152 pub const fn text_secondary() -> Color {
154 TEXT_SECONDARY
155 }
156}
157
158#[derive(Debug, Clone)]
160pub struct StatusItem {
161 pub label: String,
163 pub value: String,
165 pub style: Option<Style>,
167}
168
169impl StatusItem {
170 pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
172 Self {
173 label: label.into(),
174 value: value.into(),
175 style: None,
176 }
177 }
178
179 pub fn with_style(mut self, style: Style) -> Self {
181 self.style = Some(style);
182 self
183 }
184}
185
186#[derive(Clone)]
188pub struct DebugConfig<C: BindingContext> {
189 pub keybindings: Keybindings<C>,
191 pub debug_context: C,
193 pub style: DebugStyle,
195 status_provider: Option<StatusProvider>,
197}
198
199type StatusProvider = std::sync::Arc<dyn Fn() -> Vec<StatusItem> + Send + Sync>;
200
201impl<C: BindingContext> std::fmt::Debug for DebugConfig<C> {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 f.debug_struct("DebugConfig")
204 .field("debug_context", &self.debug_context.name())
205 .field("style", &self.style)
206 .field(
207 "status_provider",
208 &self.status_provider.as_ref().map(|_| "<fn>"),
209 )
210 .finish()
211 }
212}
213
214impl<C: BindingContext> DebugConfig<C> {
215 pub fn new(keybindings: Keybindings<C>, debug_context: C) -> Self {
217 Self {
218 keybindings,
219 debug_context,
220 style: DebugStyle::default(),
221 status_provider: None,
222 }
223 }
224
225 pub fn with_style(mut self, style: DebugStyle) -> Self {
227 self.style = style;
228 self
229 }
230
231 pub fn with_status_provider<F>(mut self, provider: F) -> Self
235 where
236 F: Fn() -> Vec<StatusItem> + Send + Sync + 'static,
237 {
238 self.status_provider = Some(std::sync::Arc::new(provider));
239 self
240 }
241
242 pub fn status_items(&self) -> Vec<StatusItem> {
244 self.status_provider
245 .as_ref()
246 .map(|f| f())
247 .unwrap_or_default()
248 }
249}
250
251pub fn default_debug_keybindings() -> Keybindings<SimpleDebugContext> {
271 default_debug_keybindings_with_toggle(&["F12", "Esc"])
272}
273
274pub fn default_debug_keybindings_with_toggle(
291 toggle_keys: &[&str],
292) -> Keybindings<SimpleDebugContext> {
293 let mut kb = Keybindings::new();
294 kb.add(
295 SimpleDebugContext::Debug,
296 "debug.toggle",
297 toggle_keys.iter().map(|s| (*s).into()).collect(),
298 );
299 kb.add(
300 SimpleDebugContext::Debug,
301 "debug.state",
302 vec!["s".into(), "S".into()],
303 );
304 kb.add(
305 SimpleDebugContext::Debug,
306 "debug.copy",
307 vec!["y".into(), "Y".into()],
308 );
309 kb.add(
310 SimpleDebugContext::Debug,
311 "debug.mouse",
312 vec!["i".into(), "I".into()],
313 );
314 kb.add(
315 SimpleDebugContext::Debug,
316 "debug.action_log",
317 vec!["a".into(), "A".into()],
318 );
319 kb
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
328 enum TestContext {
329 Debug,
330 }
331
332 impl BindingContext for TestContext {
333 fn name(&self) -> &'static str {
334 "debug"
335 }
336 fn from_name(name: &str) -> Option<Self> {
337 (name == "debug").then_some(TestContext::Debug)
338 }
339 fn all() -> &'static [Self] {
340 &[TestContext::Debug]
341 }
342 }
343
344 #[test]
345 fn test_status_item() {
346 let item = StatusItem::new("keys", "42");
347 assert_eq!(item.label, "keys");
348 assert_eq!(item.value, "42");
349 assert!(item.style.is_none());
350
351 let styled = item.with_style(Style::default().fg(Color::Red));
352 assert!(styled.style.is_some());
353 }
354
355 #[test]
356 fn test_config_with_status_provider() {
357 let config = DebugConfig::new(Keybindings::new(), TestContext::Debug)
358 .with_status_provider(|| vec![StatusItem::new("test", "value")]);
359
360 let items = config.status_items();
361 assert_eq!(items.len(), 1);
362 assert_eq!(items[0].label, "test");
363 }
364
365 #[test]
366 fn test_config_without_provider() {
367 let config: DebugConfig<TestContext> =
368 DebugConfig::new(Keybindings::new(), TestContext::Debug);
369 let items = config.status_items();
370 assert!(items.is_empty());
371 }
372
373 #[test]
374 fn test_default_debug_keybindings() {
375 let kb = default_debug_keybindings();
376 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
377
378 assert!(bindings.contains_key("debug.toggle"));
380 assert!(bindings.contains_key("debug.state"));
381 assert!(bindings.contains_key("debug.copy"));
382 assert!(bindings.contains_key("debug.mouse"));
383
384 let toggle = bindings.get("debug.toggle").unwrap();
386 assert!(toggle.contains(&"F12".to_string()));
387 assert!(toggle.contains(&"Esc".to_string()));
388
389 let state = bindings.get("debug.state").unwrap();
391 assert!(state.contains(&"s".to_string()));
392 assert!(state.contains(&"S".to_string()));
393
394 let copy = bindings.get("debug.copy").unwrap();
396 assert!(copy.contains(&"y".to_string()));
397 assert!(copy.contains(&"Y".to_string()));
398
399 let mouse = bindings.get("debug.mouse").unwrap();
401 assert!(mouse.contains(&"i".to_string()));
402 assert!(mouse.contains(&"I".to_string()));
403 }
404
405 #[test]
406 fn test_default_debug_keybindings_with_toggle_custom() {
407 let kb = default_debug_keybindings_with_toggle(&["F11"]);
408 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
409
410 let toggle = bindings.get("debug.toggle").unwrap();
412 assert!(toggle.contains(&"F11".to_string()));
413 assert!(!toggle.contains(&"F12".to_string())); let state = bindings.get("debug.state").unwrap();
417 assert!(state.contains(&"s".to_string()));
418 }
419
420 #[test]
421 fn test_default_debug_keybindings_with_toggle_multiple() {
422 let kb = default_debug_keybindings_with_toggle(&["F11", "Ctrl+D"]);
423 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
424
425 let toggle = bindings.get("debug.toggle").unwrap();
426 assert!(toggle.contains(&"F11".to_string()));
427 assert!(toggle.contains(&"Ctrl+D".to_string()));
428 }
429}