tui_dispatch_debug/debug/
config.rs1use 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)]
32pub struct DebugStyle {
33 pub banner_bg: Style,
35 pub title_style: Style,
37 pub key_styles: KeyStyles,
39 pub scrollbar: ScrollbarStyle,
41 pub label_style: Style,
43 pub value_style: Style,
45 pub dim_factor: f32,
47}
48
49#[derive(Debug, Clone)]
51pub struct ScrollbarStyle {
52 pub thumb: Style,
54 pub track: Style,
56 pub begin: Style,
58 pub end: Style,
60 pub thumb_symbol: Option<&'static str>,
62 pub track_symbol: Option<&'static str>,
64 pub begin_symbol: Option<&'static str>,
66 pub end_symbol: Option<&'static str>,
68}
69
70impl Default for ScrollbarStyle {
71 fn default() -> Self {
72 Self {
73 thumb: Style::default().fg(ACCENT_MINT),
74 track: Style::default().fg(ACCENT_MINT),
75 begin: Style::default().fg(ACCENT_MINT),
76 end: Style::default().fg(ACCENT_MINT),
77 thumb_symbol: Some("█"),
78 track_symbol: Some("│"),
79 begin_symbol: None,
80 end_symbol: None,
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct KeyStyles {
88 pub toggle: Style,
90 pub state: Style,
92 pub copy: Style,
94 pub mouse: Style,
96 pub actions: Style,
98}
99
100impl Default for KeyStyles {
101 fn default() -> Self {
102 let key_base = |bg: Color| {
103 Style::default()
104 .fg(BG_DEEP)
105 .bg(bg)
106 .add_modifier(Modifier::BOLD)
107 };
108 Self {
109 toggle: key_base(NEON_PINK),
110 state: key_base(NEON_CYAN),
111 copy: key_base(NEON_AMBER),
112 mouse: key_base(ELECTRIC_BLUE),
113 actions: key_base(KINDA_GREEN),
114 }
115 }
116}
117
118impl Default for DebugStyle {
119 fn default() -> Self {
120 Self {
121 banner_bg: Style::default().bg(BG_DEEP),
122 title_style: Style::default()
123 .fg(BG_DEEP)
124 .bg(NEON_PURPLE)
125 .add_modifier(Modifier::BOLD),
126 key_styles: KeyStyles::default(),
127 scrollbar: ScrollbarStyle::default(),
128 label_style: Style::default().fg(TEXT_SECONDARY),
129 value_style: Style::default().fg(TEXT_PRIMARY),
130 dim_factor: 0.7,
131 }
132 }
133}
134
135impl DebugStyle {
137 pub const fn neon_purple() -> Color {
139 NEON_PURPLE
140 }
141 pub const fn neon_cyan() -> Color {
143 NEON_CYAN
144 }
145 pub const fn neon_amber() -> Color {
147 NEON_AMBER
148 }
149 pub const fn neon_green() -> Color {
151 NEON_GREEN
152 }
153 pub const fn accent() -> Color {
155 ACCENT_MINT
156 }
157 pub const fn bg_deep() -> Color {
159 BG_DEEP
160 }
161 pub const fn bg_panel() -> Color {
163 BG_PANEL
164 }
165 pub const fn bg_surface() -> Color {
167 BG_SURFACE
168 }
169 pub const fn bg_highlight() -> Color {
171 BG_HIGHLIGHT
172 }
173 pub const fn overlay_bg() -> Color {
175 OVERLAY_BG
176 }
177 pub const fn overlay_bg_alt() -> Color {
179 OVERLAY_BG_ALT
180 }
181 pub const fn overlay_bg_dark() -> Color {
183 OVERLAY_BG_DARK
184 }
185 pub const fn text_primary() -> Color {
187 TEXT_PRIMARY
188 }
189 pub const fn text_secondary() -> Color {
191 TEXT_SECONDARY
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct StatusItem {
198 pub label: String,
200 pub value: String,
202 pub style: Option<Style>,
204}
205
206impl StatusItem {
207 pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
209 Self {
210 label: label.into(),
211 value: value.into(),
212 style: None,
213 }
214 }
215
216 pub fn with_style(mut self, style: Style) -> Self {
218 self.style = Some(style);
219 self
220 }
221}
222
223#[derive(Clone)]
225pub struct DebugConfig<C: BindingContext> {
226 pub keybindings: Keybindings<C>,
228 pub debug_context: C,
230 pub style: DebugStyle,
232 status_provider: Option<StatusProvider>,
234}
235
236type StatusProvider = std::sync::Arc<dyn Fn() -> Vec<StatusItem> + Send + Sync>;
237
238impl<C: BindingContext> std::fmt::Debug for DebugConfig<C> {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 f.debug_struct("DebugConfig")
241 .field("debug_context", &self.debug_context.name())
242 .field("style", &self.style)
243 .field(
244 "status_provider",
245 &self.status_provider.as_ref().map(|_| "<fn>"),
246 )
247 .finish()
248 }
249}
250
251impl<C: BindingContext> DebugConfig<C> {
252 pub fn new(keybindings: Keybindings<C>, debug_context: C) -> Self {
254 Self {
255 keybindings,
256 debug_context,
257 style: DebugStyle::default(),
258 status_provider: None,
259 }
260 }
261
262 pub fn with_style(mut self, style: DebugStyle) -> Self {
264 self.style = style;
265 self
266 }
267
268 pub fn with_status_provider<F>(mut self, provider: F) -> Self
272 where
273 F: Fn() -> Vec<StatusItem> + Send + Sync + 'static,
274 {
275 self.status_provider = Some(std::sync::Arc::new(provider));
276 self
277 }
278
279 pub fn status_items(&self) -> Vec<StatusItem> {
281 self.status_provider
282 .as_ref()
283 .map(|f| f())
284 .unwrap_or_default()
285 }
286}
287
288pub fn default_debug_keybindings() -> Keybindings<SimpleDebugContext> {
308 default_debug_keybindings_with_toggle(&["F12", "Esc"])
309}
310
311pub fn default_debug_keybindings_with_toggle(
328 toggle_keys: &[&str],
329) -> Keybindings<SimpleDebugContext> {
330 let mut kb = Keybindings::new();
331 kb.add(
332 SimpleDebugContext::Debug,
333 "debug.toggle",
334 toggle_keys.iter().map(|s| (*s).into()).collect(),
335 );
336 kb.add(
337 SimpleDebugContext::Debug,
338 "debug.state",
339 vec!["s".into(), "S".into()],
340 );
341 kb.add(
342 SimpleDebugContext::Debug,
343 "debug.copy",
344 vec!["y".into(), "Y".into()],
345 );
346 kb.add(
347 SimpleDebugContext::Debug,
348 "debug.mouse",
349 vec!["i".into(), "I".into()],
350 );
351 kb.add(
352 SimpleDebugContext::Debug,
353 "debug.action_log",
354 vec!["a".into(), "A".into()],
355 );
356 kb
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
365 enum TestContext {
366 Debug,
367 }
368
369 impl BindingContext for TestContext {
370 fn name(&self) -> &'static str {
371 "debug"
372 }
373 fn from_name(name: &str) -> Option<Self> {
374 (name == "debug").then_some(TestContext::Debug)
375 }
376 fn all() -> &'static [Self] {
377 &[TestContext::Debug]
378 }
379 }
380
381 #[test]
382 fn test_status_item() {
383 let item = StatusItem::new("keys", "42");
384 assert_eq!(item.label, "keys");
385 assert_eq!(item.value, "42");
386 assert!(item.style.is_none());
387
388 let styled = item.with_style(Style::default().fg(Color::Red));
389 assert!(styled.style.is_some());
390 }
391
392 #[test]
393 fn test_config_with_status_provider() {
394 let config = DebugConfig::new(Keybindings::new(), TestContext::Debug)
395 .with_status_provider(|| vec![StatusItem::new("test", "value")]);
396
397 let items = config.status_items();
398 assert_eq!(items.len(), 1);
399 assert_eq!(items[0].label, "test");
400 }
401
402 #[test]
403 fn test_config_without_provider() {
404 let config: DebugConfig<TestContext> =
405 DebugConfig::new(Keybindings::new(), TestContext::Debug);
406 let items = config.status_items();
407 assert!(items.is_empty());
408 }
409
410 #[test]
411 fn test_default_debug_keybindings() {
412 let kb = default_debug_keybindings();
413 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
414
415 assert!(bindings.contains_key("debug.toggle"));
417 assert!(bindings.contains_key("debug.state"));
418 assert!(bindings.contains_key("debug.copy"));
419 assert!(bindings.contains_key("debug.mouse"));
420
421 let toggle = bindings.get("debug.toggle").unwrap();
423 assert!(toggle.contains(&"F12".to_string()));
424 assert!(toggle.contains(&"Esc".to_string()));
425
426 let state = bindings.get("debug.state").unwrap();
428 assert!(state.contains(&"s".to_string()));
429 assert!(state.contains(&"S".to_string()));
430
431 let copy = bindings.get("debug.copy").unwrap();
433 assert!(copy.contains(&"y".to_string()));
434 assert!(copy.contains(&"Y".to_string()));
435
436 let mouse = bindings.get("debug.mouse").unwrap();
438 assert!(mouse.contains(&"i".to_string()));
439 assert!(mouse.contains(&"I".to_string()));
440 }
441
442 #[test]
443 fn test_default_debug_keybindings_with_toggle_custom() {
444 let kb = default_debug_keybindings_with_toggle(&["F11"]);
445 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
446
447 let toggle = bindings.get("debug.toggle").unwrap();
449 assert!(toggle.contains(&"F11".to_string()));
450 assert!(!toggle.contains(&"F12".to_string())); let state = bindings.get("debug.state").unwrap();
454 assert!(state.contains(&"s".to_string()));
455 }
456
457 #[test]
458 fn test_default_debug_keybindings_with_toggle_multiple() {
459 let kb = default_debug_keybindings_with_toggle(&["F11", "Ctrl+D"]);
460 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
461
462 let toggle = bindings.get("debug.toggle").unwrap();
463 assert!(toggle.contains(&"F11".to_string()));
464 assert!(toggle.contains(&"Ctrl+D".to_string()));
465 }
466}