Skip to main content

llm_manager/tui/app/
panels.rs

1use super::types::{ActivePanel, App, ModelsMode};
2
3impl App {
4    /// Check if a panel is visible.
5    pub fn is_panel_visible(&self, index: u8) -> bool {
6        self.ui.panel_visibility & (1 << index) != 0
7    }
8
9    /// Toggle visibility of a panel.
10    pub fn toggle_panel_visibility(&mut self, index: u8) {
11        self.ui.panel_visibility ^= 1 << index;
12        // If hiding the log while expanded, collapse it.
13        if index == 5 && !self.is_panel_visible(5) {
14            self.log.log_expanded = false;
15        }
16    }
17
18    /// Return a list of all currently visible and focusable panels in logical order.
19    pub fn get_visible_panels(&self) -> Vec<ActivePanel> {
20        let mut visible = Vec::new();
21
22        // 1. Models (Left Top)
23        if self.is_panel_visible(0) {
24            visible.push(ActivePanel::Models);
25        }
26
27        // 3. Right Panel (README / Settings / Profiles / Presets)
28        let is_search = matches!(self.models_mode, ModelsMode::Search { .. });
29        let is_files = matches!(self.models_mode, ModelsMode::Files { .. });
30        let is_bench_tune = matches!(self.models_mode, ModelsMode::BenchTune);
31        let show_readme = match &self.models_mode {
32            ModelsMode::Search { show_readme, .. } => *show_readme,
33            ModelsMode::Files { .. } => true,
34            _ => false,
35        };
36
37        if self.ui.active_panel == ActivePanel::Profiles {
38            visible.push(ActivePanel::Profiles);
39        } else if self.ui.active_panel == ActivePanel::SystemPromptPresets {
40            visible.push(ActivePanel::SystemPromptPresets);
41        } else if show_readme && (is_search || is_files) {
42            visible.push(ActivePanel::SearchReadme);
43        } else {
44            if self.is_panel_visible(1) && self.server.server_handle.is_none() && !is_bench_tune {
45                visible.push(ActivePanel::ServerSettings);
46            }
47            if self.is_panel_visible(3) {
48                visible.push(ActivePanel::LlmSettings);
49            }
50        }
51
52        // 4. Active Model (Bottom Middle) — read-only, not focusable
53
54        // 5. Log (Bottom)
55        if self.is_panel_visible(5) {
56            visible.push(ActivePanel::Log);
57        }
58
59        // 6. Downloads (Bottom, shown when downloading)
60        if self.download.downloading {
61            visible.push(ActivePanel::Downloads);
62        }
63
64        visible
65    }
66
67    pub fn focus_next(&mut self) {
68        let visible = self.get_visible_panels();
69        if visible.is_empty() {
70            return;
71        }
72
73        let current_idx = visible
74            .iter()
75            .position(|&p| p == self.ui.active_panel)
76            .unwrap_or(0);
77        let next_idx = (current_idx + 1) % visible.len();
78        self.ui.active_panel = visible[next_idx];
79    }
80
81    pub fn focus_prev(&mut self) {
82        let visible = self.get_visible_panels();
83        if visible.is_empty() {
84            return;
85        }
86
87        let current_idx = visible
88            .iter()
89            .position(|&p| p == self.ui.active_panel)
90            .unwrap_or(0);
91        let prev_idx = (current_idx + visible.len() - 1) % visible.len();
92        self.ui.active_panel = visible[prev_idx];
93    }
94}