arct_tui/panels/
notification.rs1use crate::icons;
4use crate::theme::Theme;
5use arct_core::Achievement;
6use ratatui::{
7 layout::{Alignment, Constraint, Direction, Layout, Rect},
8 text::{Line, Span},
9 widgets::{Block, Borders, Clear, Paragraph},
10 Frame,
11};
12
13pub struct NotificationPanel {
15 achievement: Achievement,
16}
17
18impl NotificationPanel {
19 pub fn new(achievement: Achievement) -> Self {
20 Self { achievement }
21 }
22
23 pub fn render(&self, frame: &mut Frame, theme: &Theme) {
25 let area = Self::centered_rect(50, 30, frame.size());
26
27 frame.render_widget(Clear, area);
29
30 let block = Block::default()
32 .title(" ACHIEVEMENT UNLOCKED! ")
33 .title_alignment(Alignment::Center)
34 .borders(Borders::ALL)
35 .border_style(theme.style_accent().add_modifier(ratatui::style::Modifier::BOLD));
36
37 let inner = block.inner(area);
38 frame.render_widget(block, area);
39
40 let chunks = Layout::default()
42 .direction(Direction::Vertical)
43 .constraints([
44 Constraint::Length(3), Constraint::Length(4), Constraint::Min(2), Constraint::Length(3), ])
49 .split(inner);
50
51 let celebration = Paragraph::new(vec![
53 Line::from(""),
54 Line::from(vec![
55 Span::styled("", theme.style_accent()),
56 Span::raw(" "),
57 Span::styled("", theme.style_accent()),
58 Span::raw(" "),
59 Span::styled("", theme.style_accent()),
60 ]),
61 ])
62 .alignment(Alignment::Center);
63 frame.render_widget(celebration, chunks[0]);
64
65 let achievement_info = Paragraph::new(vec![
67 Line::from(""),
68 Line::from(vec![
69 Span::styled(
70 self.achievement.icon,
71 theme.style_accent().add_modifier(ratatui::style::Modifier::BOLD),
72 ),
73 Span::raw(" "),
74 Span::styled(
75 &self.achievement.title,
76 theme.style_accent().add_modifier(ratatui::style::Modifier::BOLD),
77 ),
78 ]),
79 ])
80 .alignment(Alignment::Center);
81 frame.render_widget(achievement_info, chunks[1]);
82
83 let description = Paragraph::new(vec![
85 Line::from(""),
86 Line::from(vec![Span::styled(
87 &self.achievement.description,
88 theme.style_secondary(),
89 )]),
90 ])
91 .alignment(Alignment::Center);
92 frame.render_widget(description, chunks[2]);
93
94 let footer = Paragraph::new(vec![
96 Line::from(vec![
97 icons::celebration(),
98 Span::styled(
99 format!("+{} points!", self.achievement.points),
100 theme.style_accent(),
101 ),
102 ]),
103 Line::from(""),
104 Line::from(vec![
105 Span::styled("Press any key to continue", theme.style_dim()),
106 ]),
107 ])
108 .alignment(Alignment::Center);
109 frame.render_widget(footer, chunks[3]);
110
111 if area.width > 4 && area.height > 2 {
114 frame.render_widget(
116 Paragraph::new("").block(
117 Block::default()
118 .borders(Borders::NONE)
119 .border_style(theme.style_accent()),
120 ),
121 Rect::new(area.x, area.y, 2, 1),
122 );
123 frame.render_widget(
124 Paragraph::new("").block(
125 Block::default()
126 .borders(Borders::NONE)
127 .border_style(theme.style_accent()),
128 ),
129 Rect::new(area.x + area.width - 2, area.y, 2, 1),
130 );
131 }
132 }
133
134 fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
136 let popup_layout = Layout::default()
137 .direction(Direction::Vertical)
138 .constraints([
139 Constraint::Percentage((100 - percent_y) / 2),
140 Constraint::Percentage(percent_y),
141 Constraint::Percentage((100 - percent_y) / 2),
142 ])
143 .split(r);
144
145 Layout::default()
146 .direction(Direction::Horizontal)
147 .constraints([
148 Constraint::Percentage((100 - percent_x) / 2),
149 Constraint::Percentage(percent_x),
150 Constraint::Percentage((100 - percent_x) / 2),
151 ])
152 .split(popup_layout[1])[1]
153 }
154}