use std::borrow::Cow;
use ratatui::text::{Text, Line};
use crate::prelude::*;
use super::*;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Radio {
pub name: Cow<'static, str>,
pub items: Vec<Cow<'static, str>>,
pub selected: usize,
focus: usize,
}
impl Field for Radio {
type Value = usize;
type Builder = Builder;
fn name(&self) -> &str {
&self.name
}
fn input(&mut self, key: KeyEvent) -> InputResult {
match key.code {
KeyCode::Up if self.focus > 0 => {
self.focus -= 1;
InputResult::Consumed
}
KeyCode::Down if self.focus < (self.items.len() - 1) => {
self.focus += 1;
InputResult::Consumed
}
KeyCode::Up | KeyCode::Down => InputResult::Ignored,
_ => {
self.selected = self.focus;
InputResult::Updated
}
}
}
fn format(&self, focused: bool) -> Text {
let format_item = |(i, item)| {
let symbol = match i == self.selected {
true => '●',
false => ' ',
};
match focused && i == self.focus {
true => format!("[{symbol}] {item}"),
false => format!("({symbol}) {item}"),
}
};
self.items
.iter()
.enumerate()
.map(format_item)
.map(Line::from)
.collect::<Vec<_>>()
.into()
}
fn value(&self) -> &Self::Value {
&self.selected
}
fn into_value(self) -> Self::Value {
self.selected
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Builder<const NAME: bool = false, const ITEMS: bool = false>(Radio);
impl Default for Builder {
fn default() -> Self {
Self(Radio {
name: Default::default(),
items: Default::default(),
selected: 0,
focus: 0,
})
}
}
impl<const NAME: bool, const ITEMS: bool> Builder<NAME, ITEMS> {
pub fn name(self, name: impl Into<Cow<'static, str>>) -> Builder<true, ITEMS> {
let name = name.into();
Builder(Radio{ name, ..self.0 })
}
pub fn items<T>(self, items: impl IntoIterator<Item = T>) -> Builder<NAME, true>
where
T: Into<Cow<'static, str>>,
{
let items: Vec<_> = items
.into_iter()
.map(Into::into)
.collect();
debug_assert!(!items.is_empty());
Builder(Radio{ items, ..self.0 })
}
}
impl<const NAME: bool> Builder<NAME, true> {
pub fn selected(self, index: usize) -> Self {
let selected = index;
Builder(Radio{ selected, ..self.0 })
}
}
impl Build for Builder<true, true> {
type Field = Radio;
fn build(self) -> Self::Field {
self.0
}
}
#[cfg(test)]
mod tests {
use crate::{prelude::*, field::*};
#[test]
fn input() {
let input = |key: KeyCode, radio: &mut Radio, expected: InputResult| {
let actual = radio.input(key.into());
assert_eq!(actual, expected);
};
let radio = &mut Radio::builder()
.name("")
.items(["One", "Two", "Three", "Four"])
.selected(0)
.build();
assert_eq!(radio.selected, 0);
input(KeyCode::Char('a'), radio, InputResult::Updated);
assert_eq!(radio.selected, 0);
input(KeyCode::Down, radio, InputResult::Consumed);
assert_eq!(radio.selected, 0);
input(KeyCode::Enter, radio, InputResult::Updated);
assert_eq!(radio.selected, 1);
for i in 1..=2 {
assert_eq!(radio.focus, i);
input(KeyCode::Down, radio, InputResult::Consumed);
}
assert_eq!(radio.focus, 3);
input(KeyCode::Down, radio, InputResult::Ignored);
assert_eq!(radio.focus, 3);
input(KeyCode::Backspace, radio, InputResult::Updated);
assert_eq!(radio.selected, 3);
for i in (1..=3).rev() {
assert_eq!(radio.focus, i);
input(KeyCode::Up, radio, InputResult::Consumed);
}
assert_eq!(radio.focus, 0);
input(KeyCode::Up, radio, InputResult::Ignored);
assert_eq!(radio.focus, 0);
input(KeyCode::F(1), radio, InputResult::Updated);
assert_eq!(radio.selected, 0);
}
}