bluetui/
notification.rs

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}