ducker/components/
boolean_modal.rs1use std::{fmt::Debug, sync::Arc};
2
3use futures::lock::Mutex;
4use itertools::Itertools;
5
6use color_eyre::eyre::Result;
7use ratatui::{
8 layout::Rect,
9 style::{Modifier, Style},
10 text::{Line, Span, Text},
11 widgets::{Paragraph, Wrap},
12 Frame,
13};
14
15use crate::{
16 events::{message::MessageResponse, Key},
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 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}