Skip to main content

vtcode_tui/core_tui/widgets/
panel.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Modifier, Style},
5    widgets::{Block, BorderType, Widget},
6};
7
8use super::layout_mode::LayoutMode;
9use crate::ui::tui::session::styling::SessionStyles;
10use crate::ui::tui::session::terminal_capabilities;
11
12/// A consistent panel wrapper that applies standardized chrome (borders, titles)
13///
14/// This widget ensures visual consistency across all panels in the UI by
15/// providing a unified border and title style based on the active theme.
16///
17/// # Example
18/// ```ignore
19/// let inner = Panel::new(&styles)
20///     .title("Transcript")
21///     .active(true)
22///     .mode(layout_mode)
23///     .render_and_get_inner(area, buf);
24/// // Render child widget into `inner`
25/// ```
26pub struct Panel<'a> {
27    styles: &'a SessionStyles,
28    title: Option<&'a str>,
29    active: bool,
30    mode: LayoutMode,
31    border_type: Option<BorderType>,
32}
33
34impl<'a> Panel<'a> {
35    /// Create a new panel with required style reference
36    pub fn new(styles: &'a SessionStyles) -> Self {
37        Self {
38            styles,
39            title: None,
40            active: false,
41            mode: LayoutMode::Standard,
42            border_type: None,
43        }
44    }
45
46    /// Set the panel title (displayed in the border)
47    #[must_use]
48    pub fn title(mut self, title: &'a str) -> Self {
49        self.title = Some(title);
50        self
51    }
52
53    /// Mark the panel as active (highlighted border)
54    #[must_use]
55    pub fn active(mut self, active: bool) -> Self {
56        self.active = active;
57        self
58    }
59
60    /// Set the layout mode (affects border visibility)
61    #[must_use]
62    pub fn mode(mut self, mode: LayoutMode) -> Self {
63        self.mode = mode;
64        self
65    }
66
67    /// Override the border type
68    #[must_use]
69    pub fn border_type(mut self, border_type: BorderType) -> Self {
70        self.border_type = Some(border_type);
71        self
72    }
73
74    /// Render the panel and return the inner area for child widgets
75    pub fn render_and_get_inner(self, area: Rect, buf: &mut Buffer) -> Rect {
76        if !self.mode.show_borders() {
77            return area;
78        }
79
80        let border_style = if self.active {
81            self.styles.accent_style()
82        } else {
83            self.styles.border_style()
84        };
85
86        let border_type = self
87            .border_type
88            .unwrap_or_else(terminal_capabilities::get_border_type);
89
90        let mut block = Block::bordered()
91            .border_type(border_type)
92            .style(self.styles.default_style())
93            .border_style(border_style);
94
95        if self.mode.show_titles()
96            && let Some(title) = self.title
97        {
98            let title_style = if self.active {
99                self.styles.accent_style().add_modifier(Modifier::BOLD)
100            } else {
101                self.styles.default_style().add_modifier(Modifier::BOLD)
102            };
103            block = block.title(title).title_style(title_style);
104        }
105
106        let inner = block.inner(area);
107        block.render(area, buf);
108        inner
109    }
110}
111
112/// Extended style methods for panels and visual hierarchy
113pub trait PanelStyles {
114    /// Style for muted/secondary content
115    fn muted_style(&self) -> Style;
116
117    /// Style for panel titles
118    fn title_style(&self) -> Style;
119
120    /// Style for active/focused borders
121    fn border_active_style(&self) -> Style;
122
123    /// Style for dividers between sections
124    fn divider_style(&self) -> Style;
125}
126
127impl PanelStyles for SessionStyles {
128    fn muted_style(&self) -> Style {
129        self.default_style().add_modifier(Modifier::DIM)
130    }
131
132    fn title_style(&self) -> Style {
133        self.accent_style().add_modifier(Modifier::BOLD)
134    }
135
136    fn border_active_style(&self) -> Style {
137        self.border_style()
138            .remove_modifier(Modifier::DIM)
139            .add_modifier(Modifier::BOLD)
140    }
141
142    fn divider_style(&self) -> Style {
143        self.border_style().add_modifier(Modifier::DIM)
144    }
145}