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 ELECTRIC_BLUE: Color = Color::Rgb(80, 180, 255);
13
14const BG_DEEP: Color = Color::Rgb(12, 14, 22);
15const BG_PANEL: Color = Color::Rgb(18, 21, 32);
16const BG_SURFACE: Color = Color::Rgb(26, 30, 44);
17
18const TEXT_PRIMARY: Color = Color::Rgb(240, 240, 245);
19const TEXT_SECONDARY: Color = Color::Rgb(150, 150, 160);
20
21#[derive(Debug, Clone)]
23pub struct DebugStyle {
24 pub banner_bg: Style,
26 pub title_style: Style,
28 pub key_styles: KeyStyles,
30 pub label_style: Style,
32 pub value_style: Style,
34 pub dim_factor: f32,
36}
37
38#[derive(Debug, Clone)]
40pub struct KeyStyles {
41 pub toggle: Style,
43 pub state: Style,
45 pub copy: Style,
47 pub mouse: Style,
49}
50
51impl Default for KeyStyles {
52 fn default() -> Self {
53 let key_base = |bg: Color| {
54 Style::default()
55 .fg(BG_DEEP)
56 .bg(bg)
57 .add_modifier(Modifier::BOLD)
58 };
59 Self {
60 toggle: key_base(NEON_PINK),
61 state: key_base(NEON_CYAN),
62 copy: key_base(NEON_AMBER),
63 mouse: key_base(ELECTRIC_BLUE),
64 }
65 }
66}
67
68impl Default for DebugStyle {
69 fn default() -> Self {
70 Self {
71 banner_bg: Style::default().bg(BG_DEEP),
72 title_style: Style::default()
73 .fg(BG_DEEP)
74 .bg(NEON_PURPLE)
75 .add_modifier(Modifier::BOLD),
76 key_styles: KeyStyles::default(),
77 label_style: Style::default().fg(TEXT_SECONDARY),
78 value_style: Style::default().fg(TEXT_PRIMARY),
79 dim_factor: 0.7,
80 }
81 }
82}
83
84impl DebugStyle {
86 pub const fn neon_purple() -> Color {
88 NEON_PURPLE
89 }
90 pub const fn neon_cyan() -> Color {
92 NEON_CYAN
93 }
94 pub const fn neon_amber() -> Color {
96 NEON_AMBER
97 }
98 pub const fn bg_deep() -> Color {
100 BG_DEEP
101 }
102 pub const fn bg_panel() -> Color {
104 BG_PANEL
105 }
106 pub const fn bg_surface() -> Color {
108 BG_SURFACE
109 }
110 pub const fn text_primary() -> Color {
112 TEXT_PRIMARY
113 }
114 pub const fn text_secondary() -> Color {
116 TEXT_SECONDARY
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct StatusItem {
123 pub label: String,
125 pub value: String,
127 pub style: Option<Style>,
129}
130
131impl StatusItem {
132 pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
134 Self {
135 label: label.into(),
136 value: value.into(),
137 style: None,
138 }
139 }
140
141 pub fn with_style(mut self, style: Style) -> Self {
143 self.style = Some(style);
144 self
145 }
146}
147
148#[derive(Clone)]
150pub struct DebugConfig<C: BindingContext> {
151 pub keybindings: Keybindings<C>,
153 pub debug_context: C,
155 pub style: DebugStyle,
157 status_provider: Option<StatusProvider>,
159}
160
161type StatusProvider = std::sync::Arc<dyn Fn() -> Vec<StatusItem> + Send + Sync>;
162
163impl<C: BindingContext> std::fmt::Debug for DebugConfig<C> {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 f.debug_struct("DebugConfig")
166 .field("debug_context", &self.debug_context.name())
167 .field("style", &self.style)
168 .field(
169 "status_provider",
170 &self.status_provider.as_ref().map(|_| "<fn>"),
171 )
172 .finish()
173 }
174}
175
176impl<C: BindingContext> DebugConfig<C> {
177 pub fn new(keybindings: Keybindings<C>, debug_context: C) -> Self {
179 Self {
180 keybindings,
181 debug_context,
182 style: DebugStyle::default(),
183 status_provider: None,
184 }
185 }
186
187 pub fn with_style(mut self, style: DebugStyle) -> Self {
189 self.style = style;
190 self
191 }
192
193 pub fn with_status_provider<F>(mut self, provider: F) -> Self
197 where
198 F: Fn() -> Vec<StatusItem> + Send + Sync + 'static,
199 {
200 self.status_provider = Some(std::sync::Arc::new(provider));
201 self
202 }
203
204 pub fn status_items(&self) -> Vec<StatusItem> {
206 self.status_provider
207 .as_ref()
208 .map(|f| f())
209 .unwrap_or_default()
210 }
211}
212
213pub fn default_debug_keybindings() -> Keybindings<SimpleDebugContext> {
233 default_debug_keybindings_with_toggle(&["F12", "Esc"])
234}
235
236pub fn default_debug_keybindings_with_toggle(
253 toggle_keys: &[&str],
254) -> Keybindings<SimpleDebugContext> {
255 let mut kb = Keybindings::new();
256 kb.add(
257 SimpleDebugContext::Debug,
258 "debug.toggle",
259 toggle_keys.iter().map(|s| (*s).into()).collect(),
260 );
261 kb.add(
262 SimpleDebugContext::Debug,
263 "debug.state",
264 vec!["s".into(), "S".into()],
265 );
266 kb.add(
267 SimpleDebugContext::Debug,
268 "debug.copy",
269 vec!["y".into(), "Y".into()],
270 );
271 kb.add(
272 SimpleDebugContext::Debug,
273 "debug.mouse",
274 vec!["i".into(), "I".into()],
275 );
276 kb
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
285 enum TestContext {
286 Debug,
287 }
288
289 impl BindingContext for TestContext {
290 fn name(&self) -> &'static str {
291 "debug"
292 }
293 fn from_name(name: &str) -> Option<Self> {
294 (name == "debug").then_some(TestContext::Debug)
295 }
296 fn all() -> &'static [Self] {
297 &[TestContext::Debug]
298 }
299 }
300
301 #[test]
302 fn test_status_item() {
303 let item = StatusItem::new("keys", "42");
304 assert_eq!(item.label, "keys");
305 assert_eq!(item.value, "42");
306 assert!(item.style.is_none());
307
308 let styled = item.with_style(Style::default().fg(Color::Red));
309 assert!(styled.style.is_some());
310 }
311
312 #[test]
313 fn test_config_with_status_provider() {
314 let config = DebugConfig::new(Keybindings::new(), TestContext::Debug)
315 .with_status_provider(|| vec![StatusItem::new("test", "value")]);
316
317 let items = config.status_items();
318 assert_eq!(items.len(), 1);
319 assert_eq!(items[0].label, "test");
320 }
321
322 #[test]
323 fn test_config_without_provider() {
324 let config: DebugConfig<TestContext> =
325 DebugConfig::new(Keybindings::new(), TestContext::Debug);
326 let items = config.status_items();
327 assert!(items.is_empty());
328 }
329
330 #[test]
331 fn test_default_debug_keybindings() {
332 let kb = default_debug_keybindings();
333 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
334
335 assert!(bindings.contains_key("debug.toggle"));
337 assert!(bindings.contains_key("debug.state"));
338 assert!(bindings.contains_key("debug.copy"));
339 assert!(bindings.contains_key("debug.mouse"));
340
341 let toggle = bindings.get("debug.toggle").unwrap();
343 assert!(toggle.contains(&"F12".to_string()));
344 assert!(toggle.contains(&"Esc".to_string()));
345
346 let state = bindings.get("debug.state").unwrap();
348 assert!(state.contains(&"s".to_string()));
349 assert!(state.contains(&"S".to_string()));
350
351 let copy = bindings.get("debug.copy").unwrap();
353 assert!(copy.contains(&"y".to_string()));
354 assert!(copy.contains(&"Y".to_string()));
355
356 let mouse = bindings.get("debug.mouse").unwrap();
358 assert!(mouse.contains(&"i".to_string()));
359 assert!(mouse.contains(&"I".to_string()));
360 }
361
362 #[test]
363 fn test_default_debug_keybindings_with_toggle_custom() {
364 let kb = default_debug_keybindings_with_toggle(&["F11"]);
365 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
366
367 let toggle = bindings.get("debug.toggle").unwrap();
369 assert!(toggle.contains(&"F11".to_string()));
370 assert!(!toggle.contains(&"F12".to_string())); let state = bindings.get("debug.state").unwrap();
374 assert!(state.contains(&"s".to_string()));
375 }
376
377 #[test]
378 fn test_default_debug_keybindings_with_toggle_multiple() {
379 let kb = default_debug_keybindings_with_toggle(&["F11", "Ctrl+D"]);
380 let bindings = kb.get_context_bindings(SimpleDebugContext::Debug).unwrap();
381
382 let toggle = bindings.get("debug.toggle").unwrap();
383 assert!(toggle.contains(&"F11".to_string()));
384 assert!(toggle.contains(&"Ctrl+D".to_string()));
385 }
386}