ghostscope_ui/model/
ui_state.rs

1use crate::action::PanelType;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum LayoutMode {
5    Horizontal,
6    Vertical,
7}
8
9/// Command history configuration
10#[derive(Debug, Clone)]
11pub struct HistoryConfig {
12    /// Enable/disable command history file functionality
13    pub enabled: bool,
14    /// Maximum number of history entries to keep
15    pub max_entries: usize,
16}
17
18impl Default for HistoryConfig {
19    fn default() -> Self {
20        Self {
21            enabled: true,
22            max_entries: 5000,
23        }
24    }
25}
26
27/// UI configuration passed from the main crate
28#[derive(Debug, Clone)]
29pub struct UiConfig {
30    pub layout_mode: LayoutMode,
31    pub panel_ratios: [u16; 3], // [Source, EbpfInfo, InteractiveCommand]
32    pub show_source_panel: bool,
33    pub two_panel_ratios: [u16; 2], // [EbpfInfo, InteractiveCommand] when source hidden
34    pub default_focus: crate::action::PanelType,
35    pub history: HistoryConfig,
36    pub ebpf_max_messages: usize,
37}
38
39/// UI-specific state management
40#[derive(Debug)]
41pub struct UIState {
42    pub layout: LayoutState,
43    pub focus: FocusState,
44    pub config: UiConfig,
45}
46
47impl UIState {
48    pub fn new(ui_config: UiConfig) -> Self {
49        // If source panel is hidden but default focus is Source, fallback to InteractiveCommand
50        let effective_default = if !ui_config.show_source_panel
51            && matches!(ui_config.default_focus, PanelType::Source)
52        {
53            PanelType::InteractiveCommand
54        } else {
55            ui_config.default_focus
56        };
57
58        Self {
59            layout: LayoutState::new(ui_config.layout_mode),
60            focus: FocusState::new_with_default(effective_default),
61            config: ui_config,
62        }
63    }
64
65    // Backward compatibility method
66    pub fn new_with_layout_mode(layout_mode: LayoutMode) -> Self {
67        Self::new(UiConfig {
68            layout_mode,
69            panel_ratios: [4, 3, 3], // Default ratios
70            show_source_panel: true,
71            two_panel_ratios: [3, 3],
72            default_focus: crate::action::PanelType::InteractiveCommand,
73            history: HistoryConfig::default(),
74            ebpf_max_messages: 2000, // Default value
75        })
76    }
77}
78
79#[derive(Debug)]
80pub struct LayoutState {
81    pub mode: LayoutMode,
82    pub is_fullscreen: bool,
83}
84
85impl LayoutState {
86    pub fn new(mode: LayoutMode) -> Self {
87        Self {
88            mode,
89            is_fullscreen: false,
90        }
91    }
92
93    pub fn toggle_fullscreen(&mut self) {
94        self.is_fullscreen = !self.is_fullscreen;
95    }
96
97    pub fn switch_mode(&mut self) {
98        self.mode = match self.mode {
99            LayoutMode::Horizontal => LayoutMode::Vertical,
100            LayoutMode::Vertical => LayoutMode::Horizontal,
101        };
102    }
103}
104
105#[derive(Debug)]
106pub struct FocusState {
107    pub current_panel: PanelType,
108    pub expecting_window_nav: bool,
109}
110
111impl FocusState {
112    pub fn new() -> Self {
113        Self {
114            current_panel: PanelType::InteractiveCommand,
115            expecting_window_nav: false,
116        }
117    }
118
119    pub fn new_with_default(default_panel: PanelType) -> Self {
120        Self {
121            current_panel: default_panel,
122            expecting_window_nav: false,
123        }
124    }
125
126    pub fn cycle_next(&mut self, source_enabled: bool) {
127        self.current_panel = match (self.current_panel, source_enabled) {
128            (PanelType::Source, _) => PanelType::EbpfInfo,
129            (PanelType::EbpfInfo, _) => PanelType::InteractiveCommand,
130            (PanelType::InteractiveCommand, true) => PanelType::Source,
131            (PanelType::InteractiveCommand, false) => PanelType::EbpfInfo,
132        };
133    }
134
135    pub fn cycle_previous(&mut self, source_enabled: bool) {
136        self.current_panel = match (self.current_panel, source_enabled) {
137            (PanelType::Source, _) => PanelType::InteractiveCommand,
138            (PanelType::EbpfInfo, true) => PanelType::Source,
139            (PanelType::EbpfInfo, false) => PanelType::InteractiveCommand,
140            (PanelType::InteractiveCommand, _) => PanelType::EbpfInfo,
141        };
142    }
143
144    pub fn set_panel(&mut self, panel: PanelType) {
145        self.current_panel = panel;
146    }
147
148    pub fn is_focused(&self, panel: PanelType) -> bool {
149        self.current_panel == panel
150    }
151
152    pub fn move_focus_in_direction(
153        &mut self,
154        direction: crate::action::WindowDirection,
155        layout_mode: LayoutMode,
156        source_enabled: bool,
157    ) {
158        use crate::action::WindowDirection;
159
160        match layout_mode {
161            LayoutMode::Horizontal => {
162                match direction {
163                    WindowDirection::Left => {
164                        // Left motion
165                        self.current_panel = match (self.current_panel, source_enabled) {
166                            (PanelType::InteractiveCommand, _) => PanelType::EbpfInfo,
167                            (PanelType::EbpfInfo, true) => PanelType::Source,
168                            (PanelType::EbpfInfo, false) => PanelType::InteractiveCommand,
169                            (PanelType::Source, _) => PanelType::InteractiveCommand, // Wrap to rightmost
170                        };
171                    }
172                    WindowDirection::Right => {
173                        // Right motion
174                        self.current_panel = match (self.current_panel, source_enabled) {
175                            (PanelType::Source, _) => PanelType::EbpfInfo,
176                            (PanelType::EbpfInfo, _) => PanelType::InteractiveCommand,
177                            (PanelType::InteractiveCommand, true) => PanelType::Source, // Wrap to leftmost
178                            (PanelType::InteractiveCommand, false) => PanelType::EbpfInfo,
179                        };
180                    }
181                    _ => {} // Up/Down not relevant in horizontal layout
182                }
183            }
184            LayoutMode::Vertical => {
185                match direction {
186                    WindowDirection::Up => {
187                        self.current_panel = match (self.current_panel, source_enabled) {
188                            (PanelType::InteractiveCommand, _) => PanelType::EbpfInfo,
189                            (PanelType::EbpfInfo, true) => PanelType::Source,
190                            (PanelType::EbpfInfo, false) => PanelType::InteractiveCommand,
191                            (PanelType::Source, _) => PanelType::InteractiveCommand, // Wrap to bottom
192                        };
193                    }
194                    WindowDirection::Down => {
195                        self.current_panel = match (self.current_panel, source_enabled) {
196                            (PanelType::Source, _) => PanelType::EbpfInfo,
197                            (PanelType::EbpfInfo, _) => PanelType::InteractiveCommand,
198                            (PanelType::InteractiveCommand, true) => PanelType::Source, // Wrap to top
199                            (PanelType::InteractiveCommand, false) => PanelType::EbpfInfo,
200                        };
201                    }
202                    _ => {} // Left/Right not relevant in vertical layout
203                }
204            }
205        }
206    }
207}
208
209impl Default for FocusState {
210    fn default() -> Self {
211        Self::new()
212    }
213}