use std::marker::PhantomData;
use crossterm::event::KeyCode;
use crossterm::event::KeyModifiers;
use ratatui::buffer::Buffer;
use ratatui::layout::Alignment;
use ratatui::layout::Position;
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use ratatui::widgets::Widget;
use crate::widgets::Modal;
use crate::widgets::ModalConfig;
use crate::Result;
pub trait ModalSelectResponse<EVENT>
{
fn selected(
event: EVENT,
value: String,
) -> Self;
fn waiting_input() -> Self;
fn canceled(event: EVENT) -> Self;
}
#[derive(Debug)]
pub struct ModalSelect<RESPONSE, EVENT>
{
pub message: String,
pub options: Vec<String>,
selected: usize,
phantom_r: PhantomData<RESPONSE>,
phantom_e: PhantomData<EVENT>,
}
impl<RESPONSE, EVENT> ModalSelect<RESPONSE, EVENT>
{
pub fn new<S>(
message: S,
options: Vec<String>,
) -> Self
where
S: AsRef<str>,
{
Self {
message: message
.as_ref()
.to_string(),
options,
selected: 0,
phantom_r: PhantomData,
phantom_e: PhantomData,
}
}
}
impl<RESPONSE, EVENT> Modal<RESPONSE, EVENT> for ModalSelect<RESPONSE, EVENT>
where
EVENT: std::fmt::Debug,
RESPONSE: std::fmt::Debug + ModalSelectResponse<EVENT>,
{
fn config(&self) -> ModalConfig
{
ModalConfig::new().title(" SELECT ")
}
fn render(
&mut self,
area: Rect,
buf: &mut Buffer,
)
{
let mut inner_area = area;
inner_area.y += 1;
inner_area.height -= 1;
let lines = textwrap::wrap(
&self.message,
inner_area
.width
.saturating_sub(4) as usize,
)
.iter()
.map(|s| Line::raw(s.to_string()))
.collect::<Vec<_>>();
let count = lines.len();
Paragraph::new(lines)
.alignment(Alignment::Center)
.render(
inner_area, buf,
);
inner_area.y += u16::try_from(count).unwrap_or_default();
inner_area.height = 1;
for (index, option) in self
.options
.iter()
.enumerate()
{
inner_area.y += 1;
let mut paragraph =
Paragraph::new(vec![Line::raw(option)]).alignment(Alignment::Center);
if index == self.selected
{
paragraph = paragraph.style(Style::new().on_dark_gray());
}
else
{
paragraph = paragraph.style(Style::new().reset());
}
paragraph.render(
inner_area, buf,
);
}
let mut commands_area = area;
commands_area.y = commands_area.y + commands_area.height - 2;
commands_area.height = 1;
Paragraph::new("[O]k [C]ancel")
.alignment(Alignment::Center)
.render(
commands_area,
buf,
);
}
fn handle_key_event(
&mut self,
code: KeyCode,
_modifiers: KeyModifiers,
event: EVENT,
) -> Result<RESPONSE>
{
let skip = RESPONSE::waiting_input();
let response = match code
{
KeyCode::Down =>
{
if self.selected
== self
.options
.len()
- 1
{
self.selected = 0;
}
else
{
self.selected += 1;
}
skip
}
KeyCode::Up =>
{
if self.selected == 0
{
self.selected = self
.options
.len()
- 1;
}
else
{
self.selected -= 1;
}
skip
}
KeyCode::Char('c') | KeyCode::Esc => RESPONSE::canceled(event),
KeyCode::Char('o') | KeyCode::Enter =>
{
if let Some(value) = self
.options
.get(self.selected)
.cloned()
{
RESPONSE::selected(
event, value,
)
}
else
{
skip
}
}
_ => skip,
};
Ok(response)
}
fn cursor(&self) -> Option<Position>
{
None
}
}