use std::io;
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
Frame,
layout::Rect,
text::Line,
widgets::{Paragraph, Wrap},
};
use unicode_width::UnicodeWidthStr;
use super::ModalSignal;
use super::render::{hint_block, hinted_box_height};
use crate::{
input::{self},
layout::{centered_rect, fit},
overlay::{self, PopupFlow, popup},
terminal::Tui,
theme::Skin,
};
pub fn confirm(
tui: &mut Tui,
skin: &Skin,
prompt: &str,
render_bg: impl Fn(&mut Frame),
) -> io::Result<ModalSignal<bool>> {
confirm_default(tui, skin, &Question::new(prompt), render_bg)
}
#[derive(Debug, Clone, Copy)]
pub struct Question<'a> {
pub prompt: &'a str,
pub default_yes: bool,
}
impl<'a> Question<'a> {
#[must_use]
pub fn new(prompt: &'a str) -> Self {
Self {
prompt,
default_yes: true,
}
}
#[must_use]
pub fn declining(prompt: &'a str) -> Self {
Self {
prompt,
default_yes: false,
}
}
pub(super) fn hints(&self) -> [(&'static str, &'static str); 2] {
if self.default_yes {
[("enter/y", "yes"), ("n", "no")]
} else {
[("y", "yes"), ("enter/n", "no")]
}
}
}
pub fn confirm_default(
tui: &mut Tui,
skin: &Skin,
question: &Question<'_>,
render_bg: impl Fn(&mut Frame),
) -> io::Result<ModalSignal<bool>> {
let prompt = question.prompt;
let default_yes = question.default_yes;
let mut state = ();
popup(
tui,
&mut state,
|area, (): &()| {
let width = fit(prompt.width() as u16 + 6, 28, area.width);
centered_rect(width, hinted_box_height(), area)
},
|frame, (): &()| render_bg(frame),
|frame, rect, (): &()| render_confirm(frame, skin, question, rect),
|(): &mut (), key| confirm_key(key, default_yes),
)
}
pub(super) fn confirm_key(key: KeyEvent, default_yes: bool) -> PopupFlow<bool> {
match key.code {
KeyCode::Char('y' | 'Y') if input::is_bare_character(key) => {
PopupFlow::Done(true)
}
KeyCode::Char('n' | 'N') if input::is_bare_character(key) => {
PopupFlow::Done(false)
}
KeyCode::Esc => PopupFlow::Done(false),
KeyCode::Enter => PopupFlow::Done(default_yes),
_ => PopupFlow::Continue,
}
}
fn render_confirm(
frame: &mut Frame,
skin: &Skin,
question: &Question<'_>,
rect: Rect,
) {
let inner = overlay::framed(frame, rect, skin, " Confirm ");
let width = inner.width as usize;
let mut lines = vec![Line::from(question.prompt.to_string())];
lines.extend(hint_block(&question.hints(), &skin.palette, width));
let paragraph = Paragraph::new(lines).wrap(Wrap { trim: true });
frame.render_widget(paragraph, inner);
}