1use super::SimpleDebugContext;
4use ratatui::style::{Color, Modifier, Style};
5use tui_dispatch_core::keybindings::{BindingContext, Keybindings};
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 ACCENT_MINT: Color = Color::Rgb(0x36, 0xE3, 0x95);
17
18const BG_DEEP: Color = Color::Rgb(12, 14, 22);
19const BG_PANEL: Color = Color::Rgb(18, 21, 32);
20const BG_SURFACE: Color = Color::Rgb(26, 30, 44);
21const BG_HIGHLIGHT: Color = Color::Rgb(25, 30, 40);
22
23const OVERLAY_BG: Color = Color::Rgb(46, 46, 58);
24const OVERLAY_BG_ALT: Color = Color::Rgb(36, 36, 48);
25const OVERLAY_BG_DARK: Color = Color::Rgb(28, 28, 40);
26
27const TEXT_PRIMARY: Color = Color::Rgb(240, 240, 245);
28const TEXT_SECONDARY: Color = Color::Rgb(150, 150, 160);
29
30#[derive(Debug, Clone)]
46pub struct DebugStyle {
47 pub banner_bg: Style,
49 pub title_style: Style,
51 pub key_styles: KeyStyles,
53 pub scrollbar: ScrollbarStyle,
55 pub label_style: Style,
57 pub value_style: Style,
59 pub dim_factor: f32,
61
62 pub accent: Color,
65 pub text_primary: Color,
67 pub text_secondary: Color,
69 pub bg_deep: Color,
71 pub bg_surface: Color,
73 pub bg_highlight: Color,
75 pub overlay_bg: Color,
77 pub overlay_bg_alt: Color,
79 pub overlay_bg_dark: Color,
81 pub neon_purple: Color,
83 pub neon_cyan: Color,
85 pub neon_amber: Color,
87 pub neon_green: Color,
89}
90
91#[derive(Debug, Clone)]
93pub struct ScrollbarStyle {
94 pub thumb: Style,
96 pub track: Style,
98 pub begin: Style,
100 pub end: Style,
102 pub thumb_symbol: Option<&'static str>,
104 pub track_symbol: Option<&'static str>,
106 pub begin_symbol: Option<&'static str>,
108 pub end_symbol: Option<&'static str>,
110}
111
112impl Default for ScrollbarStyle {
113 fn default() -> Self {
114 Self {
115 thumb: Style::default().fg(ACCENT_MINT),
116 track: Style::default().fg(ACCENT_MINT),
117 begin: Style::default().fg(ACCENT_MINT),
118 end: Style::default().fg(ACCENT_MINT),
119 thumb_symbol: Some("█"),
120 track_symbol: Some("│"),
121 begin_symbol: None,
122 end_symbol: None,
123 }
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct KeyStyles {
130 pub toggle: Style,
132 pub state: Style,
134 pub copy: Style,
136 pub mouse: Style,
138 pub actions: Style,
140 pub components: Style,
142}
143
144impl Default for KeyStyles {
145 fn default() -> Self {
146 let key_base = |bg: Color| {
147 Style::default()
148 .fg(BG_DEEP)
149 .bg(bg)
150 .add_modifier(Modifier::BOLD)
151 };
152 Self {
153 toggle: key_base(NEON_PINK),
154 state: key_base(NEON_CYAN),
155 copy: key_base(NEON_AMBER),
156 mouse: key_base(ELECTRIC_BLUE),
157 actions: key_base(KINDA_GREEN),
158 components: key_base(NEON_PURPLE),
159 }
160 }
161}
162
163impl Default for DebugStyle {
164 fn default() -> Self {
165 Self {
166 banner_bg: Style::default().bg(BG_DEEP),
167 title_style: Style::default()
168 .fg(BG_DEEP)
169 .bg(NEON_PURPLE)
170 .add_modifier(Modifier::BOLD),
171 key_styles: KeyStyles::default(),
172 scrollbar: ScrollbarStyle::default(),
173 label_style: Style::default().fg(TEXT_SECONDARY),
174 value_style: Style::default().fg(TEXT_PRIMARY),
175 dim_factor: 0.7,
176 accent: ACCENT_MINT,
177 text_primary: TEXT_PRIMARY,
178 text_secondary: TEXT_SECONDARY,
179 bg_deep: BG_DEEP,
180 bg_surface: BG_SURFACE,
181 bg_highlight: BG_HIGHLIGHT,
182 overlay_bg: OVERLAY_BG,
183 overlay_bg_alt: OVERLAY_BG_ALT,
184 overlay_bg_dark: OVERLAY_BG_DARK,
185 neon_purple: NEON_PURPLE,
186 neon_cyan: NEON_CYAN,
187 neon_amber: NEON_AMBER,
188 neon_green: NEON_GREEN,
189 }
190 }
191}
192
193#[allow(deprecated)]
195impl DebugStyle {
196 #[deprecated(note = "use the `neon_purple` field on a DebugStyle instance")]
197 pub const fn neon_purple() -> Color {
198 NEON_PURPLE
199 }
200 #[deprecated(note = "use the `neon_cyan` field on a DebugStyle instance")]
201 pub const fn neon_cyan() -> Color {
202 NEON_CYAN
203 }
204 #[deprecated(note = "use the `neon_amber` field on a DebugStyle instance")]
205 pub const fn neon_amber() -> Color {
206 NEON_AMBER
207 }
208 #[deprecated(note = "use the `neon_green` field on a DebugStyle instance")]
209 pub const fn neon_green() -> Color {
210 NEON_GREEN
211 }
212 #[deprecated(note = "use the `accent` field on a DebugStyle instance")]
213 pub const fn accent() -> Color {
214 ACCENT_MINT
215 }
216 #[deprecated(note = "use the `bg_deep` field on a DebugStyle instance")]
217 pub const fn bg_deep() -> Color {
218 BG_DEEP
219 }
220 pub const fn bg_panel() -> Color {
222 BG_PANEL
223 }
224 #[deprecated(note = "use the `bg_surface` field on a DebugStyle instance")]
225 pub const fn bg_surface() -> Color {
226 BG_SURFACE
227 }
228 #[deprecated(note = "use the `bg_highlight` field on a DebugStyle instance")]
229 pub const fn bg_highlight() -> Color {
230 BG_HIGHLIGHT
231 }
232 #[deprecated(note = "use the `overlay_bg` field on a DebugStyle instance")]
233 pub const fn overlay_bg() -> Color {
234 OVERLAY_BG
235 }
236 #[deprecated(note = "use the `overlay_bg_alt` field on a DebugStyle instance")]
237 pub const fn overlay_bg_alt() -> Color {
238 OVERLAY_BG_ALT
239 }
240 #[deprecated(note = "use the `overlay_bg_dark` field on a DebugStyle instance")]
241 pub const fn overlay_bg_dark() -> Color {
242 OVERLAY_BG_DARK
243 }
244 #[deprecated(note = "use the `text_primary` field on a DebugStyle instance")]
245 pub const fn text_primary() -> Color {
246 TEXT_PRIMARY
247 }
248 #[deprecated(note = "use the `text_secondary` field on a DebugStyle instance")]
249 pub const fn text_secondary() -> Color {
250 TEXT_SECONDARY
251 }
252}
253
254#[derive(Debug, Clone)]
256pub struct StatusItem {
257 pub label: String,
259 pub value: String,
261 pub style: Option<Style>,
263}
264
265impl StatusItem {
266 pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
268 Self {
269 label: label.into(),
270 value: value.into(),
271 style: None,
272 }
273 }
274
275 pub fn with_style(mut self, style: Style) -> Self {
277 self.style = Some(style);
278 self
279 }
280}
281
282#[derive(Clone)]
284pub struct DebugConfig<C: BindingContext> {
285 pub keybindings: Keybindings<C>,
287 pub debug_context: C,
289 pub style: DebugStyle,
291 status_provider: Option<StatusProvider>,
293}
294
295type StatusProvider = std::sync::Arc<dyn Fn() -> Vec<StatusItem> + Send + Sync>;
296
297impl<C: BindingContext> std::fmt::Debug for DebugConfig<C> {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 f.debug_struct("DebugConfig")
300 .field("debug_context", &self.debug_context.name())
301 .field("style", &self.style)
302 .field(
303 "status_provider",
304 &self.status_provider.as_ref().map(|_| "<fn>"),
305 )
306 .finish()
307 }
308}
309
310impl<C: BindingContext> DebugConfig<C> {
311 pub fn new(keybindings: Keybindings<C>, debug_context: C) -> Self {
313 Self {
314 keybindings,
315 debug_context,
316 style: DebugStyle::default(),
317 status_provider: None,
318 }
319 }
320
321 pub fn with_style(mut self, style: DebugStyle) -> Self {
323 self.style = style;
324 self
325 }
326
327 pub fn with_status_provider<F>(mut self, provider: F) -> Self
331 where
332 F: Fn() -> Vec<StatusItem> + Send + Sync + 'static,
333 {
334 self.status_provider = Some(std::sync::Arc::new(provider));
335 self
336 }
337
338 pub fn status_items(&self) -> Vec<StatusItem> {
340 self.status_provider
341 .as_ref()
342 .map(|f| f())
343 .unwrap_or_default()
344 }
345}
346
347pub fn default_debug_keybindings() -> Keybindings<SimpleDebugContext> {
367 default_debug_keybindings_with_toggle(&["F12", "Esc"])
368}
369
370pub fn default_debug_keybindings_with_toggle(
387 toggle_keys: &[&str],
388) -> Keybindings<SimpleDebugContext> {
389 let mut kb = Keybindings::new();
390 kb.add(
391 SimpleDebugContext::Debug,
392 "debug.toggle",
393 toggle_keys.iter().map(|s| (*s).into()).collect(),
394 );
395 kb.add(
396 SimpleDebugContext::Debug,
397 "debug.state",
398 vec!["s".into(), "S".into()],
399 );
400 kb.add(
401 SimpleDebugContext::Debug,
402 "debug.copy",
403 vec!["y".into(), "Y".into()],
404 );
405 kb.add(
406 SimpleDebugContext::Debug,
407 "debug.mouse",
408 vec!["i".into(), "I".into()],
409 );
410 kb.add(
411 SimpleDebugContext::Debug,
412 "debug.action_log",
413 vec!["a".into(), "A".into()],
414 );
415 kb.add(
416 SimpleDebugContext::Debug,
417 "debug.components",
418 vec!["c".into(), "C".into()],
419 );
420 kb
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
429 enum TestContext {
430 Debug,
431 }
432
433 impl BindingContext for TestContext {
434 fn name(&self) -> &'static str {
435 "debug"
436 }
437 fn from_name(name: &str) -> Option<Self> {
438 (name == "debug").then_some(TestContext::Debug)
439 }
440 fn all() -> &'static [Self] {
441 &[TestContext::Debug]
442 }
443 }
444
445 #[test]
446 fn test_status_item() {
447 let item = StatusItem::new("keys", "42");
448 assert_eq!(item.label, "keys");
449 assert_eq!(item.value, "42");
450 assert!(item.style.is_none());
451
452 let styled = item.with_style(Style::default().fg(Color::Red));
453 assert!(styled.style.is_some());
454 }
455
456 #[test]
457 fn test_config_with_status_provider() {
458 let config = DebugConfig::new(Keybindings::new(), TestContext::Debug)
459 .with_status_provider(|| vec![StatusItem::new("test", "value")]);
460
461 let items = config.status_items();
462 assert_eq!(items.len(), 1);
463 assert_eq!(items[0].label, "test");
464 }
465
466 #[test]
467 fn test_config_without_provider() {
468 let config: DebugConfig<TestContext> =
469 DebugConfig::new(Keybindings::new(), TestContext::Debug);
470 let items = config.status_items();
471 assert!(items.is_empty());
472 }
473
474 #[test]
475 fn test_default_debug_keybindings() {
476 let kb = default_debug_keybindings();
477 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
478
479 assert!(bindings.contains_key("debug.toggle"));
481 assert!(bindings.contains_key("debug.state"));
482 assert!(bindings.contains_key("debug.copy"));
483 assert!(bindings.contains_key("debug.mouse"));
484
485 let toggle = bindings.get("debug.toggle").unwrap();
487 assert!(toggle.contains(&"F12".to_string()));
488 assert!(toggle.contains(&"Esc".to_string()));
489
490 let state = bindings.get("debug.state").unwrap();
492 assert!(state.contains(&"s".to_string()));
493 assert!(state.contains(&"S".to_string()));
494
495 let copy = bindings.get("debug.copy").unwrap();
497 assert!(copy.contains(&"y".to_string()));
498 assert!(copy.contains(&"Y".to_string()));
499
500 let mouse = bindings.get("debug.mouse").unwrap();
502 assert!(mouse.contains(&"i".to_string()));
503 assert!(mouse.contains(&"I".to_string()));
504 }
505
506 #[test]
507 fn test_default_debug_keybindings_with_toggle_custom() {
508 let kb = default_debug_keybindings_with_toggle(&["F11"]);
509 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
510
511 let toggle = bindings.get("debug.toggle").unwrap();
513 assert!(toggle.contains(&"F11".to_string()));
514 assert!(!toggle.contains(&"F12".to_string())); let state = bindings.get("debug.state").unwrap();
518 assert!(state.contains(&"s".to_string()));
519 }
520
521 #[test]
522 fn test_default_debug_keybindings_with_toggle_multiple() {
523 let kb = default_debug_keybindings_with_toggle(&["F11", "Ctrl+D"]);
524 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
525
526 let toggle = bindings.get("debug.toggle").unwrap();
527 assert!(toggle.contains(&"F11".to_string()));
528 assert!(toggle.contains(&"Ctrl+D".to_string()));
529 }
530}