Skip to main content

wiki_tui/components/
message_popup.rs

1use std::sync::Arc;
2
3use crossterm::event::{KeyCode, KeyEvent};
4use ratatui::{
5    layout::{Alignment, Rect},
6    style::{Style, Stylize},
7    text::Line,
8    widgets::{Block, Clear},
9};
10
11use crate::{
12    action::{Action, ActionPacket, ActionResult},
13    config::Theme,
14    ui::centered_rect,
15};
16
17use super::Component;
18
19pub struct MessagePopupComponent {
20    title: String,
21    content: String,
22    content_alignment: Alignment,
23
24    theme: Arc<Theme>,
25
26    confirmation: Option<ActionPacket>,
27}
28
29impl MessagePopupComponent {
30    pub fn new_raw(title: String, content: String, theme: Arc<Theme>) -> Self {
31        Self {
32            title,
33            content,
34            content_alignment: Alignment::Center,
35
36            theme,
37
38            confirmation: None,
39        }
40    }
41
42    pub fn new_error(error: String, theme: Arc<Theme>) -> Self {
43        const ERROR_MESSAGE: &str =
44            "An error occurred\nCheck the logs for further information\n\nError: {ERROR}";
45
46        Self {
47            title: "Error".to_string(),
48            content: ERROR_MESSAGE.replace("{ERROR}", &error),
49            content_alignment: Alignment::Left,
50
51            theme,
52
53            confirmation: None,
54        }
55    }
56
57    pub fn new_confirmation(
58        title: String,
59        content: String,
60        cb: ActionPacket,
61        theme: Arc<Theme>,
62    ) -> Self {
63        Self {
64            title,
65            content,
66            content_alignment: Alignment::Center,
67
68            theme,
69
70            confirmation: Some(cb),
71        }
72    }
73}
74
75impl Component for MessagePopupComponent {
76    fn handle_key_events(&mut self, key: KeyEvent) -> ActionResult {
77        match key.code {
78            KeyCode::Char('y') if self.confirmation.is_some() => self
79                .confirmation
80                .take()
81                .unwrap()
82                .action(Action::PopPopup)
83                .into(),
84            KeyCode::Char('n') if self.confirmation.is_some() => Action::PopPopup.into(),
85
86            KeyCode::Esc => Action::PopPopup.into(),
87            _ => ActionResult::Ignored,
88        }
89    }
90
91    fn render(&mut self, f: &mut crate::terminal::Frame<'_>, area: ratatui::prelude::Rect) {
92        let max_area = centered_rect(area, 50, 80);
93
94        let width = (max_area.width as usize).min(self.content.chars().count() + 2) as usize;
95        let wrapped_message = textwrap::wrap(&self.content, width);
96
97        let height = (max_area.height as usize).min(wrapped_message.len() + 2);
98
99        let area = Rect {
100            x: area.x + (area.width - width as u16) / 2,
101            y: area.y + (area.height - height as u16) / 2,
102            width: width as u16,
103            height: height as u16,
104        };
105
106        f.render_widget(Clear, area);
107        f.render_widget(
108            Block::default().style(Style::default().bg(self.theme.bg)),
109            area,
110        );
111
112        let title_line = if self.title == "Error" {
113            Line::from("Error".bold().red()).centered()
114        } else {
115            Line::from(self.title.clone()).centered()
116        };
117
118        let mut block = self.theme.default_block().title_top(title_line);
119
120        block = if self.confirmation.is_some() {
121            block
122                .title_bottom(Line::from(vec!["Y".bold(), "es".into()]).right_aligned())
123                .title_bottom(Line::from(vec!["N".bold(), "o".into()]).right_aligned())
124        } else {
125            block.title_bottom(Line::from("<ESC> Dismiss").right_aligned())
126        };
127
128        let message_widget = self
129            .theme
130            .default_paragraph(
131                wrapped_message
132                    .iter()
133                    .map(|x| Line::from(x.to_string()))
134                    .collect::<Vec<Line>>(),
135            )
136            .alignment(self.content_alignment)
137            .block(block);
138        f.render_widget(message_widget, area);
139    }
140}