Skip to main content

pito_footer/
quit.rs

1use std::borrow::Cow;
2use std::time::{Duration, Instant};
3
4use crate::confirm::{Answer, Confirm, ConfirmBar, Words};
5use crate::key::Key;
6
7pub const WINDOW: Duration = Duration::from_secs(2);
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub struct Wording {
12    pub again: Cow<'static, str>,
13    pub ask: Cow<'static, str>,
14}
15
16impl Wording {
17    pub fn new(again: impl Into<Cow<'static, str>>) -> Self {
18        Wording {
19            again: again.into(),
20            ask: Cow::Borrowed(""),
21        }
22    }
23
24    pub fn ask(mut self, ask: impl Into<Cow<'static, str>>) -> Self {
25        self.ask = ask.into();
26        self
27    }
28}
29
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum Mode {
33    #[default]
34    Twice,
35    Ask,
36    Once,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum Guard {
42    Pass,
43    Held,
44    Quit,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct QuitGuard {
49    mode: Mode,
50    window: Duration,
51    triggers: &'static [Key],
52    armed: Option<Instant>,
53    asking: Option<Confirm>,
54    confirm: Confirm,
55    wording: Wording,
56}
57
58impl QuitGuard {
59    pub fn new(wording: Wording) -> Self {
60        QuitGuard {
61            mode: Mode::Twice,
62            window: WINDOW,
63            triggers: &[Key::Ctrl('c')],
64            armed: None,
65            asking: None,
66            confirm: Confirm::new(),
67            wording,
68        }
69    }
70
71    pub fn mode(mut self, mode: Mode) -> Self {
72        self.mode = mode;
73        self
74    }
75
76    pub fn window(mut self, window: Duration) -> Self {
77        self.window = window;
78        self
79    }
80
81    pub fn triggers(mut self, triggers: &'static [Key]) -> Self {
82        self.triggers = triggers;
83        self
84    }
85
86    pub fn confirm(mut self, confirm: Confirm) -> Self {
87        self.confirm = confirm;
88        self
89    }
90
91    pub fn wording_mut(&mut self) -> &mut Wording {
92        &mut self.wording
93    }
94
95    pub fn key(&mut self, key: Key, now: Instant, busy: bool) -> Guard {
96        let trigger = self.triggers.contains(&key);
97        if let Some(confirm) = self.asking.as_mut() {
98            let answer = if trigger {
99                Some(Answer::No)
100            } else {
101                confirm.key(key)
102            };
103            return match answer {
104                Some(Answer::Yes) => {
105                    self.asking = None;
106                    Guard::Quit
107                }
108                Some(Answer::No) => {
109                    self.asking = None;
110                    Guard::Held
111                }
112                None => Guard::Held,
113            };
114        }
115        if !trigger {
116            self.armed = None;
117            return Guard::Pass;
118        }
119        if self.mode == Mode::Once {
120            return Guard::Quit;
121        }
122        let again = self
123            .armed
124            .take()
125            .is_some_and(|at| now.saturating_duration_since(at) < self.window);
126        if !again {
127            self.armed = Some(now);
128            return Guard::Held;
129        }
130        if self.mode == Mode::Ask && busy {
131            self.asking = Some(self.confirm);
132            return Guard::Held;
133        }
134        Guard::Quit
135    }
136
137    pub fn tick(&mut self, now: Instant) -> bool {
138        if self
139            .armed
140            .is_some_and(|at| now.saturating_duration_since(at) >= self.window)
141        {
142            self.armed = None;
143            return true;
144        }
145        false
146    }
147
148    pub fn deadline(&self) -> Option<Instant> {
149        self.armed.and_then(|at| at.checked_add(self.window))
150    }
151
152    pub fn armed(&self) -> bool {
153        self.armed.is_some()
154    }
155
156    pub fn notice(&self) -> Option<&str> {
157        self.armed.map(|_| self.wording.again.as_ref())
158    }
159
160    pub fn asking(&self) -> Option<&Confirm> {
161        self.asking.as_ref()
162    }
163
164    pub fn bar<'a>(&'a self, words: Words<'a>) -> Option<ConfirmBar<'a>> {
165        self.asking
166            .as_ref()
167            .map(|confirm| ConfirmBar::new(&self.wording.ask, confirm, words))
168    }
169
170    pub fn cancel(&mut self) {
171        self.armed = None;
172        self.asking = None;
173    }
174}