use std::{
borrow::Cow,
fmt::Display,
ops::{Sub, Add, RangeInclusive},
};
use num_traits::{Bounded, One, Zero};
use ratatui::{
text::{Line, Span, Text},
style::{Style, Stylize},
};
use crate::prelude::*;
use super::*;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Slider<T> {
pub name: Cow<'static, str>,
pub value: T,
pub range: RangeInclusive<T>,
pub step: T,
pub default: T,
pub prefix: Option<Cow<'static, str>>,
pub suffix: Option<Cow<'static, str>>,
}
impl<T> Field for Slider<T>
where
T: Clone + Display + PartialOrd,
Builder<T>: Default,
for<'a> &'a T: Add<Output = T> + Sub<Output = T>,
{
type Value = T;
type Builder = Builder<T>;
fn name(&self) -> &str {
&self.name
}
fn input(&mut self, key: KeyEvent) -> InputResult {
let modifier = !key.modifiers.is_empty();
self.value = match (key.code, modifier) {
(KeyCode::Left, false) if &self.value > self.range.start() => {
if self.value >= self.range.start() + &self.step {
&self.value - &self.step
} else {
self.range.start().clone()
}
}
(KeyCode::Right, false) if &self.value < self.range.end() => {
if self.value <= self.range.end() - &self.step {
&self.value + &self.step
} else {
self.range.end().clone()
}
}
(KeyCode::Left, true) if &self.value > self.range.start() => {
if self.value > self.default {
self.default.clone()
} else {
self.range.start().clone()
}
}
(KeyCode::Right, true) if &self.value < self.range.end() => {
if self.value < self.default {
self.default.clone()
} else {
self.range.end().clone()
}
}
_ => return InputResult::Ignored,
};
InputResult::Updated
}
fn format(&self, focused: bool) -> Text {
let val = format!("{}", self.value);
let style = |cond| match focused && cond {
true => Style::new().bold(),
false => Style::new(),
};
let [prefix, suffix] = [&self.prefix, &self.suffix]
.map(Option::as_ref)
.map(|x| x.map(AsRef::as_ref).map(Span::from))
.map(Option::unwrap_or_default);
Line::from(vec![
Span::styled("<", style(&self.value != self.range.start())),
prefix,
Span::styled(val, style(focused)),
suffix,
Span::styled(">", style(&self.value != self.range.end())),
]).into()
}
fn value(&self) -> &T {
&self.value
}
fn into_value(self) -> T {
self.value
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Builder<T, const NAME: bool = false>(Slider<T>);
impl<T> Default for Builder<T>
where
T: Zero + One + Bounded,
{
fn default() -> Self {
Self(Slider {
name: Default::default(),
value: T::zero(),
range: T::min_value()..=T::max_value(),
step: T::one(),
default: T::zero(),
prefix: None,
suffix: None,
})
}
}
impl<T, const NAME: bool> Builder<T, NAME> {
pub fn name(self, name: impl Into<Cow<'static, str>>) -> Builder<T, true> {
let name = name.into();
Builder(Slider{ name, ..self.0 })
}
pub fn value(self, value: T) -> Self
where
T: Clone,
{
let default = value.clone();
Builder(Slider{ value, default, ..self.0 })
}
pub fn range(self, range: RangeInclusive<T>) -> Self
where
T: Clone + PartialOrd,
{
let (min, max) = range.clone().into_inner();
let value = self.0.value.clone();
let value = match (value < min, value > max) {
(true, _) => min,
(_, true) => max,
(_, _) => value,
};
Builder(Slider{ range, ..self.0 }).value(value)
}
pub fn step(self, step: T) -> Self {
Builder(Slider{ step, ..self.0 })
}
pub fn prefix(self, prefix: impl Into<Cow<'static, str>>) -> Self {
let prefix = Some(prefix.into());
Builder(Slider{ prefix, ..self.0 })
}
pub fn suffix(self, suffix: impl Into<Cow<'static, str>>) -> Self {
let suffix = Some(suffix.into());
Builder(Slider{ suffix, ..self.0 })
}
}
impl<T> Build for Builder<T, true>
where
Slider<T>: Field
{
type Field = Slider<T>;
fn build(self) -> Slider<T> {
self.0
}
}