use std::io; use std::time::{Duration, Instant}; use crossterm::event; use crossterm::event::{Event, KeyCode}; use tui::backend::Backend; use tui::layout::*; use tui::text::*; use tui::widgets::*; use tui::{Frame, Terminal}; use crate::constants::*; use crate::ui_screens::utils::centered_rect_size; use crate::ui_screens::ScreenResult; use crate::ui_widgets::button_widget::ButtonWidget; /// Convenience function to ask for user confirmation pub fn confirm(terminal: &mut Terminal, msg: &str) -> bool { matches!( ConfirmDialogScreen::new(msg) .show(terminal) .unwrap_or(ScreenResult::Canceled), ScreenResult::Ok(true) ) } pub struct ConfirmDialogScreen<'a> { title: &'a str, msg: &'a str, is_confirm: bool, can_cancel: bool, } impl<'a> ConfirmDialogScreen<'a> { pub fn new(msg: &'a str) -> Self { Self { title: "Confirmation Request", msg, is_confirm: true, can_cancel: false, } } pub fn show( mut self, terminal: &mut Terminal, ) -> io::Result> { let mut last_tick = Instant::now(); loop { terminal.draw(|f| self.ui(f))?; let timeout = TICK_RATE .checked_sub(last_tick.elapsed()) .unwrap_or_else(|| Duration::from_secs(0)); if event::poll(timeout)? { if let Event::Key(key) = event::read()? { match key.code { KeyCode::Esc | KeyCode::Char('q') if self.can_cancel => { return Ok(ScreenResult::Canceled) } // Toggle selected choice KeyCode::Left | KeyCode::Right | KeyCode::Tab => { self.is_confirm = !self.is_confirm } // Submit choice KeyCode::Enter => { return Ok(ScreenResult::Ok(self.is_confirm)); } _ => {} } } } if last_tick.elapsed() >= TICK_RATE { last_tick = Instant::now(); } } } fn ui(&mut self, f: &mut Frame) { // Preprocess message let lines = textwrap::wrap(self.msg, f.size().width as usize - 20); let line_max_len = lines.iter().map(|l| l.len()).max().unwrap(); let area = centered_rect_size(line_max_len as u16 + 4, 5 + lines.len() as u16, &f.size()); let block = Block::default().borders(Borders::ALL).title(self.title); f.render_widget(block, area); // Create two chunks with equal horizontal screen space let chunks = Layout::default() .direction(Direction::Vertical) .constraints( [ Constraint::Length(lines.len() as u16), Constraint::Length(3), ] .as_ref(), ) .split(area.inner(&Margin { horizontal: 2, vertical: 1, })); let text = lines .iter() .map(|s| Spans::from(s.as_ref())) .collect::>(); let paragraph = Paragraph::new(text); f.render_widget(paragraph, chunks[0]); // Buttons let buttons_area = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(chunks[1]); let cancel_button = ButtonWidget::new("Cancel", true).set_disabled(self.is_confirm); f.render_widget(cancel_button, buttons_area[0]); let ok_button = ButtonWidget::new("Confirm", true).set_disabled(!self.is_confirm); f.render_widget(ok_button, buttons_area[1]); } }