Skip to main content

stynx_code_tui/widgets/
sidebar.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Modifier, Style},
5    text::{Line, Span},
6    widgets::{Paragraph, Widget, Wrap},
7};
8
9use crate::state::app_state::SidebarState;
10use crate::theme;
11
12pub struct Sidebar<'a> {
13    state: &'a SidebarState,
14}
15
16impl<'a> Sidebar<'a> {
17    pub fn new(state: &'a SidebarState) -> Self { Self { state } }
18}
19
20impl<'a> Widget for Sidebar<'a> {
21    fn render(self, area: Rect, buf: &mut Buffer) {
22        for y in area.y..area.y + area.height {
23            for x in area.x..area.x + area.width {
24                buf[(x, y)].set_style(Style::default().bg(theme::BACKGROUND_PANEL()));
25            }
26        }
27
28        let inner = Rect {
29            x: area.x + 2,
30            y: area.y + 1,
31            width: area.width.saturating_sub(4),
32            height: area.height.saturating_sub(2),
33        };
34
35        let mut lines: Vec<Line<'static>> = Vec::new();
36
37        lines.push(Line::from(Span::styled(
38            self.state.title.clone(),
39            Style::default()
40                .fg(theme::TEXT())
41                .bg(theme::BACKGROUND_PANEL())
42                .add_modifier(Modifier::BOLD),
43        )));
44        if !self.state.session_id.is_empty() {
45            lines.push(Line::from(Span::styled(
46                self.state.session_id.clone(),
47                Style::default().fg(theme::TEXT_MUTED()).bg(theme::BACKGROUND_PANEL()),
48            )));
49        }
50        lines.push(Line::from(""));
51
52        if !self.state.sessions.is_empty() {
53            lines.push(Line::from(Span::styled(
54                "Recent",
55                Style::default()
56                    .fg(theme::TEXT_MUTED())
57                    .bg(theme::BACKGROUND_PANEL())
58                    .add_modifier(Modifier::DIM),
59            )));
60            for s in self.state.sessions.iter().take(20) {
61                let prefix = if s.pinned { "★ " } else { "  " };
62                let title = if s.title.len() > 32 { format!("{}…", &s.title[..31]) } else { s.title.clone() };
63                lines.push(Line::from(vec![
64                    Span::styled(prefix, Style::default().fg(theme::WARNING()).bg(theme::BACKGROUND_PANEL())),
65                    Span::styled(title, Style::default().fg(theme::TEXT()).bg(theme::BACKGROUND_PANEL())),
66                ]));
67            }
68        }
69
70        let body_height = inner.height.saturating_sub(2);
71        let body_area = Rect { height: body_height, ..inner };
72        Paragraph::new(lines)
73            .wrap(Wrap { trim: false })
74            .style(Style::default().bg(theme::BACKGROUND_PANEL()))
75            .render(body_area, buf);
76
77        let footer_y = inner.y + inner.height.saturating_sub(1);
78        let footer_area = Rect { x: inner.x, y: footer_y, width: inner.width, height: 1 };
79        let footer = Line::from(vec![
80            Span::styled("• ", Style::default().fg(theme::SUCCESS()).bg(theme::BACKGROUND_PANEL())),
81            Span::styled("stynx", Style::default().fg(theme::TEXT_MUTED()).bg(theme::BACKGROUND_PANEL())),
82            Span::styled(
83                "-code",
84                Style::default()
85                    .fg(theme::TEXT())
86                    .bg(theme::BACKGROUND_PANEL())
87                    .add_modifier(Modifier::BOLD),
88            ),
89            Span::styled(
90                format!(" v{}", self.state.version),
91                Style::default().fg(theme::TEXT_MUTED()).bg(theme::BACKGROUND_PANEL()),
92            ),
93        ]);
94        Paragraph::new(footer)
95            .style(Style::default().bg(theme::BACKGROUND_PANEL()))
96            .render(footer_area, buf);
97    }
98}