stynx_code_tui/widgets/
sidebar.rs1use 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}