1use ratatui::backend::CrosstermBackend;
2use ratatui::crossterm::event;
3use ratatui::crossterm::event::Event;
4use ratatui::{Frame, Terminal, TerminalOptions, Viewport, crossterm};
5use std::io;
6use std::time::{Duration, Instant};
7
8pub trait Dialog: Sized {
9 type Update;
10 type Output;
11 fn update_for_event(event: Event) -> Option<Self::Update>;
12 fn perform_update(&mut self, update: Self::Update) -> io::Result<()>;
13 fn state(&self) -> DialogState;
14 fn output(self) -> Self::Output;
15 fn viewport(&self) -> Viewport;
16 fn draw(&mut self, frame: &mut Frame);
17 fn run(mut self) -> io::Result<Self::Output> {
18 let backend = CrosstermBackend::new(io::stderr());
19 let mut terminal = Terminal::with_options(
20 backend,
21 TerminalOptions {
22 viewport: self.viewport(),
23 },
24 )?;
25 crossterm::terminal::enable_raw_mode()?;
26 let start = Instant::now();
27 let mut up_to_date = false;
28 let mut timed_out = false;
29 while self.state() == DialogState::Pending {
30 if !up_to_date {
31 terminal.draw(|frame| self.draw(frame))?;
32 }
33
34 let event_available = event::poll(self.tick_rate())?;
35 if self
36 .timeout()
37 .is_some_and(|timeout| start.elapsed() > timeout)
38 {
39 timed_out = true;
40 break;
41 }
42 if !event_available {
43 up_to_date = !self.tick();
44 continue;
45 }
46
47 let event = event::read()?;
48 if let Some(update) = Self::update_for_event(event) {
49 self.perform_update(update)?;
50 up_to_date = false;
51 } else {
52 up_to_date = true;
53 }
54 }
55 terminal.clear()?;
56 let result = if timed_out {
57 Err(io::Error::other("timed out"))
58 } else if self.state() == DialogState::Cancelled {
59 Err(io::Error::other("cancelled"))
60 } else {
61 Ok(self.output())
62 };
63 crossterm::terminal::disable_raw_mode()?;
64 result
65 }
66 fn tick(&mut self) -> bool {
67 false
68 }
69 fn tick_rate(&self) -> Duration {
70 Duration::from_millis(500)
71 }
72 fn timeout(&self) -> Option<Duration> {
73 None
74 }
75}
76
77#[derive(Clone, Copy, Debug, Default, PartialEq)]
78pub enum DialogState {
79 #[default]
80 Pending,
81 Completed,
82 Cancelled,
83}