Skip to main content

ducker/components/
boolean_modal.rs

1use std::{fmt::Debug, sync::Arc};
2
3use futures::lock::Mutex;
4use itertools::Itertools;
5
6use color_eyre::eyre::Result;
7use ratatui::{
8    Frame,
9    layout::Rect,
10    style::{Modifier, Style},
11    text::{Line, Span, Text},
12    widgets::{Paragraph, Wrap},
13};
14
15use crate::{
16    events::{Key, message::MessageResponse},
17    traits::{Callback, Component, ModalComponent},
18    widgets::modal::ModalWidget,
19};
20
21#[derive(Debug, Default, PartialEq, Eq, Clone)]
22pub enum ModalState {
23    #[default]
24    Closed,
25    Open(String),
26}
27
28#[derive(Default, Debug)]
29pub struct BooleanModal<P> {
30    pub discriminator: P,
31    pub state: ModalState,
32    title: String,
33    callback: Option<Arc<Mutex<dyn Callback>>>,
34}
35
36impl<P> BooleanModal<P> {
37    pub fn new(title: String, discriminator: P) -> Self {
38        Self {
39            discriminator,
40            state: ModalState::default(),
41            title,
42            callback: None,
43        }
44    }
45
46    pub fn initialise(&mut self, message: String, cb: Option<Arc<Mutex<dyn Callback>>>) {
47        self.callback = cb;
48        self.state = ModalState::Open(message)
49    }
50
51    pub fn reset(&mut self) {
52        self.callback = None;
53        self.state = ModalState::Closed
54    }
55}
56
57#[async_trait::async_trait]
58impl<P> ModalComponent for BooleanModal<P>
59where
60    P: Debug + Send,
61{
62    async fn update(&mut self, message: Key) -> Result<MessageResponse> {
63        match message {
64            Key::Esc | Key::Char('n') | Key::Char('N') => {
65                self.reset();
66                Ok(MessageResponse::Consumed)
67            }
68            Key::Char('y') | Key::Char('Y') | Key::Enter => {
69                if let Some(cb) = self.callback.clone() {
70                    cb.lock().await.call().await?;
71                }
72                self.reset();
73                Ok(MessageResponse::Consumed)
74            }
75            // We don't want Q to be able to quit here
76            Key::Char('Q') | Key::Char('q') => Ok(MessageResponse::Consumed),
77            _ => Ok(MessageResponse::NotConsumed),
78        }
79    }
80}
81
82impl<P> Component for BooleanModal<P>
83where
84    P: std::fmt::Debug,
85{
86    fn draw(&mut self, f: &mut Frame<'_>, area: Rect) {
87        let message: String = match &self.state {
88            ModalState::Open(v) => v.clone(),
89            _ => return,
90        };
91
92        let title = Line::from(format!("< {} >", self.title.clone())).centered();
93
94        let message = Paragraph::new(Text::from(message))
95            .wrap(Wrap { trim: true })
96            .centered();
97
98        let spans = [("Y/y/Enter", "Yes"), ("N/n", "No")]
99            .iter()
100            .flat_map(|(key, desc)| {
101                let key = Span::styled(
102                    format!(" <{key}> = "),
103                    Style::new().add_modifier(Modifier::ITALIC),
104                );
105                let desc = Span::styled(
106                    format!("{desc} "),
107                    Style::new().add_modifier(Modifier::ITALIC),
108                );
109                [key, desc]
110            })
111            .collect_vec();
112
113        let modal = ModalWidget::new(title, message, spans);
114
115        f.render_widget(modal, area);
116    }
117}