use std::io;
use ratada::quit::QuitKind;
use ratada::{
ModalSignal, Screen, Tui, command_palette, finder, help, modal, quit,
};
use ratatui::Frame;
use crate::tui::App;
use crate::tui::appframe;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Answer {
Yes,
No,
ForcedQuit,
}
impl Answer {
pub fn is_yes(self) -> bool {
self == Answer::Yes
}
pub fn is_forced_quit(self) -> bool {
self == Answer::ForcedQuit
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Selection {
Index(usize),
None,
ForcedQuit,
}
pub trait Interaction {
fn confirm(&mut self, app: &App, prompt: &str) -> io::Result<Answer>;
fn help(&mut self, app: &App) -> io::Result<Answer>;
fn pick(
&mut self,
app: &App,
title: &str,
items: &[String],
) -> io::Result<Selection>;
fn palette(
&mut self,
app: &App,
items: &[command_palette::CommandItem<'_>],
) -> io::Result<Selection>;
fn may_quit(&mut self, app: &App) -> bool;
}
pub struct Modals<'a> {
tui: &'a mut Tui,
}
impl<'a> Modals<'a> {
pub fn new(tui: &'a mut Tui) -> Self {
Modals { tui }
}
}
fn answer_of(signal: ModalSignal<bool>) -> Answer {
match signal {
ModalSignal::Value(true) => Answer::Yes,
ModalSignal::Value(false) | ModalSignal::Cancelled => Answer::No,
ModalSignal::Quit => Answer::ForcedQuit,
}
}
impl Interaction for Modals<'_> {
fn confirm(&mut self, app: &App, prompt: &str) -> io::Result<Answer> {
let signal = modal::confirm(self.tui, app.skin(), prompt, |frame| {
app.render(frame);
})?;
Ok(answer_of(signal))
}
fn help(&mut self, app: &App) -> io::Result<Answer> {
let owned = app.help_sections();
let sections = appframe::to_help_sections(&owned);
let signal = help::show(self.tui, app.skin(), §ions, |frame| {
app.render(frame);
})?;
Ok(match signal {
ModalSignal::Quit => Answer::ForcedQuit,
ModalSignal::Value(()) | ModalSignal::Cancelled => Answer::No,
})
}
fn pick(
&mut self,
app: &App,
title: &str,
items: &[String],
) -> io::Result<Selection> {
let signal = finder::finder(self.tui, app.skin(), title, items, |f| {
app.render(f);
})?;
Ok(match signal {
ModalSignal::Value(index) => Selection::Index(index),
ModalSignal::Cancelled => Selection::None,
ModalSignal::Quit => Selection::ForcedQuit,
})
}
fn palette(
&mut self,
app: &App,
items: &[command_palette::CommandItem<'_>],
) -> io::Result<Selection> {
let signal = command_palette::command_palette(
self.tui,
app.skin(),
" Commands ",
items,
|f| {
app.render(f);
},
)?;
Ok(match signal {
ModalSignal::Value(index) => Selection::Index(index),
ModalSignal::Cancelled => Selection::None,
ModalSignal::Quit => Selection::ForcedQuit,
})
}
fn may_quit(&mut self, app: &App) -> bool {
let repaint = |frame: &mut Frame| app.render(frame);
quit::request(self.tui, QuitKind::Soft, &repaint)
}
}
#[cfg(test)]
pub use headless::Headless;
#[cfg(test)]
mod headless {
use super::*;
pub struct Headless {
pub answer: Answer,
pub selection: Selection,
pub asked: usize,
}
impl Headless {
pub fn accepting() -> Self {
Headless {
answer: Answer::Yes,
selection: Selection::None,
asked: 0,
}
}
pub fn declining() -> Self {
Headless {
answer: Answer::No,
selection: Selection::None,
asked: 0,
}
}
pub fn force_quitting() -> Self {
Headless {
answer: Answer::ForcedQuit,
selection: Selection::ForcedQuit,
asked: 0,
}
}
pub fn picking(mut self, index: usize) -> Self {
self.selection = Selection::Index(index);
self
}
}
impl Interaction for Headless {
fn confirm(&mut self, _app: &App, _prompt: &str) -> io::Result<Answer> {
self.asked += 1;
Ok(self.answer)
}
fn help(&mut self, _app: &App) -> io::Result<Answer> {
self.asked += 1;
Ok(self.answer)
}
fn pick(
&mut self,
_app: &App,
_title: &str,
_items: &[String],
) -> io::Result<Selection> {
self.asked += 1;
Ok(self.selection)
}
fn palette(
&mut self,
_app: &App,
_items: &[command_palette::CommandItem<'_>],
) -> io::Result<Selection> {
self.asked += 1;
Ok(self.selection)
}
fn may_quit(&mut self, _app: &App) -> bool {
true
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_forced_quit_is_neither_a_yes_nor_a_no() {
assert!(!Answer::ForcedQuit.is_yes());
assert!(Answer::ForcedQuit.is_forced_quit());
assert!(Answer::Yes.is_yes());
assert!(!Answer::No.is_forced_quit());
}
#[test]
fn the_toolkit_quit_signal_maps_to_a_forced_quit() {
assert_eq!(answer_of(ModalSignal::Quit), Answer::ForcedQuit);
assert_eq!(answer_of(ModalSignal::Value(true)), Answer::Yes);
assert_eq!(answer_of(ModalSignal::Value(false)), Answer::No);
assert_eq!(answer_of(ModalSignal::Cancelled), Answer::No);
}
}