arct_tui/panels/
notification.rs

1//! Achievement notification popup for celebrating unlocks
2
3use 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
13/// Notification panel for displaying achievement unlocks
14pub struct NotificationPanel {
15    achievement: Achievement,
16}
17
18impl NotificationPanel {
19    pub fn new(achievement: Achievement) -> Self {
20        Self { achievement }
21    }
22
23    /// Render the notification as a centered overlay popup
24    pub fn render(&self, frame: &mut Frame, theme: &Theme) {
25        let area = Self::centered_rect(50, 30, frame.size());
26
27        // Clear the background
28        frame.render_widget(Clear, area);
29
30        // Use a double border with celebration theme
31        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        // Split into sections
41        let chunks = Layout::default()
42            .direction(Direction::Vertical)
43            .constraints([
44                Constraint::Length(3),  // Celebration header
45                Constraint::Length(4),  // Achievement info
46                Constraint::Min(2),     // Description
47                Constraint::Length(3),  // Points and dismiss
48            ])
49            .split(inner);
50
51        // Celebration header
52        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        // Achievement info (icon + title)
66        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        // Description
84        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        // Points and dismiss instruction
95        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        // Add decorative border effect
112        // Draw corner decorations
113        if area.width > 4 && area.height > 2 {
114            // Top corners
115            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    /// Helper function to create a centered rectangle
135    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}