stynx_code_tui/widgets/
toast.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Modifier, Style},
5 text::{Line, Span},
6 widgets::{Clear, Paragraph, Widget},
7};
8
9use crate::state::{ToastKind, ToastState};
10use crate::theme;
11
12pub struct ToastStack<'a> {
13 pub state: &'a ToastState,
14}
15
16impl<'a> ToastStack<'a> {
17 pub fn new(state: &'a ToastState) -> Self { Self { state } }
18}
19
20impl<'a> Widget for ToastStack<'a> {
21 fn render(self, area: Rect, buf: &mut Buffer) {
22 if self.state.items.is_empty() {
23 return;
24 }
25
26 let max_w = 50.min(area.width.saturating_sub(4)) as usize;
27 let mut y = area.y + 1;
28 let right_edge = area.x + area.width.saturating_sub(2);
29
30 for toast in &self.state.items {
31 if y >= area.y + area.height { break; }
32
33 let (icon, color) = match toast.kind {
34 ToastKind::Info => ("ⓘ", theme::ACCENT()),
35 ToastKind::Success => ("✓", theme::SUCCESS()),
36 ToastKind::Warning => ("△", theme::WARNING()),
37 ToastKind::Error => ("✗", theme::ERROR()),
38 };
39
40 let trimmed: String = if toast.message.chars().count() > max_w.saturating_sub(4) {
41 let mut s: String = toast.message.chars().take(max_w.saturating_sub(5)).collect();
42 s.push('…');
43 s
44 } else {
45 toast.message.clone()
46 };
47
48 let text = format!(" {icon} {trimmed} ");
49 let width = text.chars().count() as u16;
50 let x = right_edge.saturating_sub(width);
51
52 let rect = Rect { x, y, width, height: 1 };
53 Clear.render(rect, buf);
54
55 let line = Line::from(vec![
56 Span::styled(format!(" {icon} "), Style::default().fg(color).bg(theme::BACKGROUND_ELEMENT()).add_modifier(Modifier::BOLD)),
57 Span::styled(format!("{trimmed} "), Style::default().fg(theme::TEXT()).bg(theme::BACKGROUND_ELEMENT())),
58 ]);
59 Paragraph::new(line)
60 .style(Style::default().bg(theme::BACKGROUND_ELEMENT()))
61 .render(rect, buf);
62
63 y += 1;
64 }
65 }
66}