tui_dispatch_core/debug/
config.rs

1//! Debug layer configuration
2
3use crate::keybindings::{BindingContext, Keybindings};
4use ratatui::style::{Color, Modifier, Style};
5
6/// Style configuration for debug UI
7#[derive(Debug, Clone)]
8pub struct DebugStyle {
9    /// Background style for the banner
10    pub banner_bg: Style,
11    /// Title style (e.g., "DEBUG" label)
12    pub title_style: Style,
13    /// Key hint style (e.g., "F12")
14    pub key_style: Style,
15    /// Label style (e.g., "resume")
16    pub label_style: Style,
17    /// Value style for status items
18    pub value_style: Style,
19    /// Dim factor for background (0.0-1.0)
20    pub dim_factor: f32,
21}
22
23impl Default for DebugStyle {
24    fn default() -> Self {
25        Self {
26            banner_bg: Style::default().bg(Color::Rgb(20, 20, 30)),
27            title_style: Style::default()
28                .fg(Color::Magenta)
29                .add_modifier(Modifier::BOLD),
30            key_style: Style::default().fg(Color::Rgb(20, 20, 30)).bg(Color::Cyan),
31            label_style: Style::default().fg(Color::Rgb(150, 150, 160)),
32            value_style: Style::default().fg(Color::White),
33            dim_factor: 0.7,
34        }
35    }
36}
37
38/// Status item for the debug banner
39#[derive(Debug, Clone)]
40pub struct StatusItem {
41    /// Label/key text
42    pub label: String,
43    /// Value text
44    pub value: String,
45    /// Optional custom style
46    pub style: Option<Style>,
47}
48
49impl StatusItem {
50    /// Create a new status item
51    pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
52        Self {
53            label: label.into(),
54            value: value.into(),
55            style: None,
56        }
57    }
58
59    /// Set custom style
60    pub fn with_style(mut self, style: Style) -> Self {
61        self.style = Some(style);
62        self
63    }
64}
65
66/// Configuration for the debug layer
67#[derive(Clone)]
68pub struct DebugConfig<C: BindingContext> {
69    /// Keybindings for debug commands
70    pub keybindings: Keybindings<C>,
71    /// Context used for debug-specific bindings
72    pub debug_context: C,
73    /// Style configuration
74    pub style: DebugStyle,
75    /// Status items provider (called each render)
76    status_provider: Option<StatusProvider>,
77}
78
79type StatusProvider = std::sync::Arc<dyn Fn() -> Vec<StatusItem> + Send + Sync>;
80
81impl<C: BindingContext> std::fmt::Debug for DebugConfig<C> {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("DebugConfig")
84            .field("debug_context", &self.debug_context.name())
85            .field("style", &self.style)
86            .field(
87                "status_provider",
88                &self.status_provider.as_ref().map(|_| "<fn>"),
89            )
90            .finish()
91    }
92}
93
94impl<C: BindingContext> DebugConfig<C> {
95    /// Create a new config with keybindings and debug context
96    pub fn new(keybindings: Keybindings<C>, debug_context: C) -> Self {
97        Self {
98            keybindings,
99            debug_context,
100            style: DebugStyle::default(),
101            status_provider: None,
102        }
103    }
104
105    /// Set the style
106    pub fn with_style(mut self, style: DebugStyle) -> Self {
107        self.style = style;
108        self
109    }
110
111    /// Set a status provider function
112    ///
113    /// This function is called each render to get status items for the banner.
114    pub fn with_status_provider<F>(mut self, provider: F) -> Self
115    where
116        F: Fn() -> Vec<StatusItem> + Send + Sync + 'static,
117    {
118        self.status_provider = Some(std::sync::Arc::new(provider));
119        self
120    }
121
122    /// Get status items from the provider (if any)
123    pub fn status_items(&self) -> Vec<StatusItem> {
124        self.status_provider
125            .as_ref()
126            .map(|f| f())
127            .unwrap_or_default()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    // Minimal test context
136    #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
137    enum TestContext {
138        Debug,
139    }
140
141    impl BindingContext for TestContext {
142        fn name(&self) -> &'static str {
143            "debug"
144        }
145        fn from_name(name: &str) -> Option<Self> {
146            (name == "debug").then_some(TestContext::Debug)
147        }
148        fn all() -> &'static [Self] {
149            &[TestContext::Debug]
150        }
151    }
152
153    #[test]
154    fn test_status_item() {
155        let item = StatusItem::new("keys", "42");
156        assert_eq!(item.label, "keys");
157        assert_eq!(item.value, "42");
158        assert!(item.style.is_none());
159
160        let styled = item.with_style(Style::default().fg(Color::Red));
161        assert!(styled.style.is_some());
162    }
163
164    #[test]
165    fn test_config_with_status_provider() {
166        let config = DebugConfig::new(Keybindings::new(), TestContext::Debug)
167            .with_status_provider(|| vec![StatusItem::new("test", "value")]);
168
169        let items = config.status_items();
170        assert_eq!(items.len(), 1);
171        assert_eq!(items[0].label, "test");
172    }
173
174    #[test]
175    fn test_config_without_provider() {
176        let config: DebugConfig<TestContext> =
177            DebugConfig::new(Keybindings::new(), TestContext::Debug);
178        let items = config.status_items();
179        assert!(items.is_empty());
180    }
181}