Skip to main content

stynx_code_tui/widgets/
thinking_panel.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::theme;
10use crate::widgets::spinner::FRAMES;
11
12pub struct ThinkingPanel<'a> {
13    pub text: &'a str,
14    pub spinner_frame: usize,
15}
16
17impl<'a> ThinkingPanel<'a> {
18    pub fn new(text: &'a str, spinner_frame: usize) -> Self {
19        Self { text, spinner_frame }
20    }
21}
22
23impl<'a> Widget for ThinkingPanel<'a> {
24    fn render(self, area: Rect, buf: &mut Buffer) {
25        if self.text.trim().is_empty() || area.height == 0 || area.width < 4 {
26            return;
27        }
28
29        let frame = FRAMES[self.spinner_frame % FRAMES.len()];
30
31        let visible_body = (area.height as usize).saturating_sub(1);
32        let body_lines: Vec<&str> = {
33            let mut v: Vec<&str> = self
34                .text
35                .lines()
36                .filter(|l| !l.trim().is_empty())
37                .collect();
38            if v.len() > visible_body {
39                v = v[v.len() - visible_body..].to_vec();
40            }
41            v
42        };
43
44        for y in area.y..area.y + area.height {
45            for x in area.x..area.x + area.width {
46                buf[(x, y)].set_style(Style::default().bg(theme::BACKGROUND()));
47            }
48        }
49
50        let accent = theme::IRIS();
51        let bar_col = theme::OVERLAY();
52
53        for y in area.y..area.y + area.height {
54            buf[(area.x, y)]
55                .set_char('▌')
56                .set_style(Style::default().fg(accent).bg(theme::BACKGROUND()));
57        }
58
59        let inner_x = area.x + 2;
60        let inner_width = area.width.saturating_sub(3) as usize;
61        let _ = bar_col;
62
63        let header = Line::from(vec![
64            Span::styled(
65                format!("{frame} "),
66                Style::default()
67                    .fg(accent)
68                    .add_modifier(Modifier::BOLD),
69            ),
70            Span::styled(
71                "thinking",
72                Style::default()
73                    .fg(theme::SUBTLE())
74                    .add_modifier(Modifier::ITALIC),
75            ),
76        ]);
77
78        let inner_area = Rect {
79            x: inner_x,
80            y: area.y,
81            width: inner_width as u16,
82            height: area.height,
83        };
84
85        let mut lines: Vec<Line<'static>> = vec![header];
86        for raw in body_lines {
87            let text = if raw.len() > inner_width {
88                format!("{}…", &raw[..inner_width.saturating_sub(1)])
89            } else {
90                raw.trim_end().to_string()
91            };
92            lines.push(Line::from(Span::styled(
93                text,
94                Style::default()
95                    .fg(theme::MUTED())
96                    .add_modifier(Modifier::ITALIC),
97            )));
98        }
99
100        Paragraph::new(lines)
101            .wrap(Wrap { trim: false })
102            .render(inner_area, buf);
103    }
104}