1use ratatui::{
2 Frame,
3 layout::{Alignment, Constraint, Direction, Layout, Rect},
4 style::{Color, Modifier, Style},
5 text::{Line, Text},
6 widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
7};
8use tokio::sync::mpsc::UnboundedSender;
9
10use crate::{app::AppResult, event::Event};
11
12#[derive(Debug, Clone)]
13pub struct Notification {
14 pub message: String,
15 pub level: NotificationLevel,
16 pub ttl: u16,
17}
18
19#[derive(Debug, Clone)]
20pub enum NotificationLevel {
21 Error,
22 Warning,
23 Info,
24}
25
26impl Notification {
27 pub fn render(&self, index: usize, frame: &mut Frame, area: Rect) {
28 let (color, title) = match self.level {
29 NotificationLevel::Info => (Color::Green, "Info"),
30 NotificationLevel::Warning => (Color::Yellow, "Warning"),
31 NotificationLevel::Error => (Color::Red, "Error"),
32 };
33
34 let mut text = Text::from(vec![
35 Line::from(title).style(Style::new().fg(color).add_modifier(Modifier::BOLD)),
36 ]);
37
38 text.extend(Text::from(self.message.as_str()));
39
40 let notification_height = text.height() as u16 + 2;
41 let notification_width = text.width() as u16 + 4;
42
43 let block = Paragraph::new(text)
44 .alignment(Alignment::Center)
45 .wrap(Wrap { trim: false })
46 .block(
47 Block::default()
48 .borders(Borders::ALL)
49 .style(Style::default())
50 .border_type(BorderType::Thick)
51 .border_style(Style::default().fg(color)),
52 );
53
54 let area = notification_rect(index as u16, notification_height, notification_width, area);
55
56 frame.render_widget(Clear, area);
57 frame.render_widget(block, area);
58 }
59 pub fn send(
60 message: String,
61 level: NotificationLevel,
62 sender: UnboundedSender<Event>,
63 ) -> AppResult<()> {
64 let notif = Notification {
65 message,
66 level,
67 ttl: 2,
68 };
69
70 sender.send(Event::Notification(notif))?;
71
72 Ok(())
73 }
74}
75
76pub fn notification_rect(offset: u16, height: u16, width: u16, r: Rect) -> Rect {
77 let popup_layout = Layout::default()
78 .direction(Direction::Vertical)
79 .constraints(
80 [
81 Constraint::Length(height * offset),
82 Constraint::Length(height),
83 Constraint::Min(1),
84 ]
85 .as_ref(),
86 )
87 .split(r);
88
89 Layout::default()
90 .direction(Direction::Horizontal)
91 .constraints(
92 [
93 Constraint::Min(1),
94 Constraint::Length(width),
95 Constraint::Length(2),
96 ]
97 .as_ref(),
98 )
99 .split(popup_layout[1])[1]
100}