use crate::{EditField, EditGuard, MarkButton};
use kas::event::{Command, ScrollDelta};
use kas::prelude::*;
use kas::theme::{FrameStyle, MarkStyle, TextClass};
use std::cmp::Ord;
use std::ops::RangeInclusive;
pub trait SpinnerValue:
Copy + PartialOrd + std::fmt::Debug + std::str::FromStr + ToString + 'static
{
fn default_step() -> Self;
fn clamp(self, l_bound: Self, u_bound: Self) -> Self;
fn add_step(self, step: Self, u_bound: Self) -> Self;
fn sub_step(self, step: Self, l_bound: Self) -> Self;
}
macro_rules! impl_float {
($t:ty) => {
impl SpinnerValue for $t {
fn default_step() -> Self {
1.0
}
fn clamp(self, l_bound: Self, u_bound: Self) -> Self {
<$t>::clamp(self, l_bound, u_bound)
}
fn add_step(self, step: Self, u_bound: Self) -> Self {
((self / step + 1.0).round() * step).min(u_bound)
}
fn sub_step(self, step: Self, l_bound: Self) -> Self {
((self / step - 1.0).round() * step).max(l_bound)
}
}
};
}
impl_float!(f32);
impl_float!(f64);
macro_rules! impl_int {
($t:ty) => {
impl SpinnerValue for $t {
fn default_step() -> Self {
1
}
fn clamp(self, l_bound: Self, u_bound: Self) -> Self {
Ord::clamp(self, l_bound, u_bound)
}
fn add_step(self, step: Self, u_bound: Self) -> Self {
((self / step).saturating_add(1)).saturating_mul(step).min(u_bound)
}
fn sub_step(self, step: Self, l_bound: Self) -> Self {
(((self + step - 1) / step).saturating_sub(1)).saturating_mul(step).max(l_bound)
}
}
};
($($t:ty),*) => {
$(impl_int!($t);)*
};
}
impl_int!(i8, i16, i32, i64, i128, isize);
impl_int!(u8, u16, u32, u64, u128, usize);
#[derive(Clone, Debug)]
enum SpinBtn {
Down,
Up,
}
#[derive(Debug)]
struct ValueMsg<T>(T);
#[autoimpl(Debug ignore self.state_fn where T: trait)]
struct SpinnerGuard<A, T: SpinnerValue> {
start: T,
end: T,
step: T,
parsed: Option<T>,
state_fn: Box<dyn Fn(&ConfigCx, &A) -> T>,
}
impl<A, T: SpinnerValue> SpinnerGuard<A, T> {
fn new(range: RangeInclusive<T>, state_fn: Box<dyn Fn(&ConfigCx, &A) -> T>) -> Self {
let (start, end) = range.into_inner();
SpinnerGuard {
start,
end,
step: T::default_step(),
parsed: None,
state_fn,
}
}
fn handle_btn(&self, cx: &mut EventCx, data: &A, btn: SpinBtn) -> Option<T> {
let old_value = cx.config_cx(|cx| (self.state_fn)(cx, data));
let value = match btn {
SpinBtn::Down => old_value.sub_step(self.step, self.start),
SpinBtn::Up => old_value.add_step(self.step, self.end),
};
(value != old_value).then_some(value)
}
}
impl<A, T: SpinnerValue> EditGuard for SpinnerGuard<A, T> {
type Data = A;
fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &A) {
let value = (edit.guard.state_fn)(cx, data);
*cx |= edit.set_string(value.to_string());
}
fn focus_lost(edit: &mut EditField<Self>, cx: &mut EventCx, data: &A) {
if let Some(value) = edit.guard.parsed.take() {
cx.push(ValueMsg(value));
} else {
let value = cx.config_cx(|cx| (edit.guard.state_fn)(cx, data));
*cx |= edit.set_string(value.to_string());
}
}
fn edit(edit: &mut EditField<Self>, cx: &mut EventCx, _: &A) {
let is_err;
if let Ok(value) = edit.get_str().parse::<T>() {
edit.guard.parsed = Some(value.clamp(edit.guard.start, edit.guard.end));
is_err = false;
} else {
is_err = true;
};
*cx |= edit.set_error_state(is_err);
}
}
impl_scope! {
#[widget {
layout = frame!(row! [
self.edit,
column! [self.b_up, self.b_down],
], style = FrameStyle::EditBox);
}]
pub struct Spinner<A, T: SpinnerValue> {
core: widget_core!(),
#[widget]
edit: EditField<SpinnerGuard<A, T>>,
#[widget(&())]
b_up: MarkButton<SpinBtn>,
#[widget(&())]
b_down: MarkButton<SpinBtn>,
on_change: Option<Box<dyn Fn(&mut EventCx, &A, T)>>,
}
impl Self {
#[inline]
pub fn new(range: RangeInclusive<T>, state_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self {
Spinner {
core: Default::default(),
edit: EditField::new(SpinnerGuard::new(range, Box::new(state_fn)))
.with_width_em(3.0, 8.0),
b_up: MarkButton::new_msg(MarkStyle::Point(Direction::Up), SpinBtn::Up),
b_down: MarkButton::new_msg(MarkStyle::Point(Direction::Down), SpinBtn::Down),
on_change: None,
}
}
#[inline]
pub fn new_msg<M: std::fmt::Debug + 'static>(
range: RangeInclusive<T>,
state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
msg_fn: impl Fn(T) -> M + 'static,
) -> Self {
Spinner::new(range, state_fn).with_msg(msg_fn)
}
#[inline]
#[must_use]
pub fn with_msg<M>(self, f: impl Fn(T) -> M + 'static) -> Self
where
M: std::fmt::Debug + 'static,
{
self.with(move |cx, _, state| cx.push(f(state)))
}
#[inline]
#[must_use]
pub fn with(mut self, f: impl Fn(&mut EventCx, &A, T) + 'static) -> Self {
debug_assert!(self.on_change.is_none());
self.on_change = Some(Box::new(f));
self
}
#[inline]
#[must_use]
pub fn with_class(mut self, class: TextClass) -> Self {
self.edit = self.edit.with_class(class);
self
}
#[inline]
pub fn class(&self) -> TextClass {
self.edit.class()
}
#[inline]
pub fn set_width_em(&mut self, min_em: f32, ideal_em: f32) {
self.edit.set_width_em(min_em, ideal_em);
}
#[inline]
#[must_use]
pub fn with_width_em(mut self, min_em: f32, ideal_em: f32) -> Self {
self.set_width_em(min_em, ideal_em);
self
}
#[inline]
#[must_use]
pub fn with_step(mut self, step: T) -> Self {
self.edit.guard.step = step;
self
}
}
impl Layout for Self {
fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) {
<Self as kas::layout::AutoLayout>::set_rect(self, cx, rect);
self.edit.set_outer_rect(rect, FrameStyle::EditBox);
}
fn find_id(&mut self, coord: Coord) -> Option<WidgetId> {
self.b_up.find_id(coord)
.or_else(|| self.b_down.find_id(coord))
.or_else(|| self.edit.find_id(coord))
}
fn draw(&mut self, mut draw: DrawCx) {
draw.recurse(&mut self.edit);
draw.recurse(&mut self.b_up);
draw.recurse(&mut self.b_down);
}
}
impl Events for Self {
type Data = A;
fn steal_event(&mut self, cx: &mut EventCx, data: &A, _: &WidgetId, event: &Event) -> IsUsed {
let btn = match event {
Event::Command(cmd, code) => match cmd {
Command::Down => {
if let Some(code) = code {
cx.depress_with_key(self.b_down.id(), *code);
}
SpinBtn::Down
}
Command::Up => {
if let Some(code) = code {
cx.depress_with_key(self.b_up.id(), *code);
}
SpinBtn::Up
}
_ => return Unused,
},
Event::Scroll(ScrollDelta::LineDelta(_, y)) => {
if *y > 0.0 {
SpinBtn::Up
} else if *y < 0.0 {
SpinBtn::Down
} else {
return Unused;
}
}
_ => return Unused,
};
if let Some(value) = self.edit.guard.handle_btn(cx, data, btn) {
if let Some(ref f) = self.on_change {
f(cx, data, value);
}
}
Used
}
fn handle_messages(&mut self, cx: &mut EventCx, data: &A) {
let new_value = if let Some(ValueMsg(value)) = cx.try_pop() {
Some(value)
} else if let Some(btn) = cx.try_pop::<SpinBtn>() {
self.edit.guard.handle_btn(cx, data, btn)
} else {
None
};
if let Some(value) = new_value {
if let Some(ref f) = self.on_change {
f(cx, data, value);
}
}
}
}
}