use std::{borrow::Cow, ops::RangeInclusive, iter};
use ratatui::text::{Text, Line};
use crate::prelude::*;
use super::*;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Toggle {
pub name: Cow<'static, str>,
pub range: RangeInclusive<usize>,
focus: usize,
items: Vec<Cow<'static, str>>,
values: Vec<bool>,
log: Vec<Option<usize>>,
time: usize,
}
impl Toggle {
pub fn set_items<T>(&mut self, items: impl IntoIterator<Item = T>)
where
T: Into<Cow<'static, str>>,
{
self.items = items
.into_iter()
.map(Into::into)
.collect();
debug_assert!(!self.items.is_empty());
let min_toggled = self.range
.start()
.clone();
let values = iter::repeat(true)
.take(min_toggled)
.chain(iter::repeat(false))
.take(self.items.len());
self.set_values(values);
}
pub fn set_values(&mut self, values: impl IntoIterator<Item = bool>) {
let (values, log) = values
.into_iter()
.map(|b| (b, b.then_some(self.time)))
.unzip();
self.values = values;
self.log = log;
debug_assert!(self.values.len() == self.items.len());
}
pub fn items(&self) -> &[Cow<'static, str>] {
&self.items
}
pub fn toggle_on(&mut self, index: usize) -> Result<(), ()> {
if &self.pop_count() >= self.range.end() {
let oldest = self.log
.iter()
.enumerate()
.filter_map(|(i, time)| time.map(|time| (i, time)))
.min_by_key(|&(_, time)| time)
.map(|(i, _)| i);
let Some(oldest) = oldest else {
return Err(())
};
self.values[oldest] = false;
self.log[oldest] = None;
}
self.time += 1;
self.values[index] = true;
self.log[index] = Some(self.time);
Ok(())
}
pub fn toggle_off(&mut self, index: usize) -> Result<(), ()> {
if &self.pop_count() > self.range.start() {
self.values[index] = false;
self.log[index] = None;
Ok(())
} else {
Err(())
}
}
pub fn pop_count(&self) -> usize {
self.values
.iter()
.filter(|&&t| t)
.count()
}
}
impl Field for Toggle {
type Value = Vec<bool>;
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,
_ => {
let result = match self.values[self.focus] {
true => self.toggle_off(self.focus),
false => self.toggle_on(self.focus),
};
match result {
Ok(_) => InputResult::Updated,
Err(_) => InputResult::Ignored,
}
}
}
}
fn format(&self, focused: bool) -> Text {
let format_item = |i, item, toggled| {
let symbol = match toggled {
true => '◼',
false => ' ',
};
match focused && i == self.focus {
true => format!("[{symbol}] {item}"),
false => format!("({symbol}) {item}"),
}
};
iter::zip(self.items.iter(), self.values.iter())
.enumerate()
.map(|(i, (item, &value))| format_item(i, item, value))
.map(Line::from)
.collect::<Vec<_>>()
.into()
}
fn value(&self) -> &Self::Value {
&self.values
}
fn into_value(self) -> Self::Value {
self.values
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Builder<const NAME: bool = false, const ITEMS: bool = false>(Toggle);
impl Default for Builder {
fn default() -> Self {
Self(Toggle {
name: Default::default(),
range: 0..=usize::MAX,
focus: 0,
items: Default::default(),
values: Default::default(),
log: Default::default(),
time: 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(Toggle{ name, ..self.0 })
}
pub fn items<T>(mut self, items: impl IntoIterator<Item = T>) -> Builder<NAME, true>
where
T: Into<Cow<'static, str>>,
{
self.0.set_items(items);
Builder(self.0)
}
}
impl<const NAME: bool> Builder<NAME, true> {
pub fn values(mut self, values: impl IntoIterator<Item = bool>) -> Self {
self.0.set_values(values);
Builder(self.0)
}
pub fn range(mut self, range: RangeInclusive<usize>) -> Self {
let min = range
.start()
.clone();
let difference = min.saturating_sub(self.0.pop_count());
let free = iter::zip(self.0.values.iter_mut(), self.0.log.iter_mut())
.filter(|(&mut b, _)| !b)
.take(difference);
for (value, log) in free {
*value = true;
*log = Some(0)
}
Builder(Toggle{ range, ..self.0 })
}
}
impl Build for Builder<true, true> {
type Field = Toggle;
fn build(self) -> Toggle {
self.0
}
}