use crate::icons::{
ICON_CHEVRON_DOWN, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT, ICON_CHEVRON_UP, ICON_MINUS,
ICON_PLUS,
};
use crate::prelude::*;
pub(crate) enum SpinboxEvent {
Increment,
Decrement,
SetMin,
SetMax,
}
pub struct Spinbox {
value: Signal<f64>,
orientation: Signal<Orientation>,
icons: Signal<SpinboxIcons>,
min: Signal<Option<f64>>,
max: Signal<Option<f64>>,
on_change: Option<Box<dyn Fn(&mut EventContext, f64)>>,
on_decrement: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
on_increment: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SpinboxIcons {
PlusMinus,
Chevrons,
}
impl_res_simple!(SpinboxIcons);
impl Spinbox {
pub fn new<S, T>(cx: &mut Context, value: S) -> Handle<Spinbox>
where
S: Copy + SignalGet<T> + SignalMap<T> + Res<T> + 'static,
T: Clone + Into<f64> + 'static,
{
let numeric_value = value.map(|v| v.clone().into()).to_signal(cx);
let orientation = Signal::new(Orientation::Horizontal);
let icons = Signal::new(SpinboxIcons::Chevrons);
let min = Signal::new(None::<f64>);
let max = Signal::new(None::<f64>);
Self {
value: numeric_value,
orientation,
icons,
min,
max,
on_change: None,
on_decrement: None,
on_increment: None,
}
.build(cx, move |cx| {
Keymap::from(vec![
(
KeyChord::new(Modifiers::empty(), Code::ArrowUp),
KeymapEntry::new("Increment", |cx| cx.emit(SpinboxEvent::Increment)),
),
(
KeyChord::new(Modifiers::empty(), Code::ArrowRight),
KeymapEntry::new("Increment", |cx| cx.emit(SpinboxEvent::Increment)),
),
(
KeyChord::new(Modifiers::empty(), Code::ArrowDown),
KeymapEntry::new("Decrement", |cx| cx.emit(SpinboxEvent::Decrement)),
),
(
KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
KeymapEntry::new("Decrement", |cx| cx.emit(SpinboxEvent::Decrement)),
),
(
KeyChord::new(Modifiers::empty(), Code::Home),
KeymapEntry::new("Set Min", |cx| cx.emit(SpinboxEvent::SetMin)),
),
(
KeyChord::new(Modifiers::empty(), Code::End),
KeymapEntry::new("Set Max", |cx| cx.emit(SpinboxEvent::SetMax)),
),
])
.build(cx);
let at_min = Memo::new(move |_| {
matches!((min.get(), numeric_value.get()), (Some(min), value) if value <= min)
});
let at_max = Memo::new(move |_| {
matches!((max.get(), numeric_value.get()), (Some(max), value) if value >= max)
});
Binding::new(cx, orientation, move |cx| match orientation.get() {
Orientation::Horizontal => {
Button::new(cx, |cx| {
Svg::new(
cx,
icons.map(|icons| match icons {
SpinboxIcons::PlusMinus => ICON_MINUS,
SpinboxIcons::Chevrons => ICON_CHEVRON_LEFT,
}),
)
})
.on_press(|ex| ex.emit(SpinboxEvent::Decrement))
.disabled(at_min)
.navigable(false)
.name(Localized::new("decrement"))
.variant(ButtonVariant::Text)
.class("spinbox-button");
}
Orientation::Vertical => {
Button::new(cx, |cx| {
Svg::new(
cx,
icons.map(|icons| match icons {
SpinboxIcons::PlusMinus => ICON_PLUS,
SpinboxIcons::Chevrons => ICON_CHEVRON_UP,
}),
)
})
.on_press(|ex| ex.emit(SpinboxEvent::Increment))
.disabled(at_max)
.navigable(false)
.name(Localized::new("increment"))
.variant(ButtonVariant::Text)
.class("spinbox-button");
}
});
Textbox::new(cx, numeric_value).class("spinbox-value").role(Role::SpinButton);
Binding::new(cx, orientation, move |cx| match orientation.get() {
Orientation::Horizontal => {
Button::new(cx, |cx| {
Svg::new(
cx,
icons.map(|icons| match icons {
SpinboxIcons::PlusMinus => ICON_PLUS,
SpinboxIcons::Chevrons => ICON_CHEVRON_RIGHT,
}),
)
})
.on_press(|ex| ex.emit(SpinboxEvent::Increment))
.disabled(at_max)
.navigable(false)
.name(Localized::new("increment"))
.variant(ButtonVariant::Text)
.class("spinbox-button");
}
Orientation::Vertical => {
Button::new(cx, |cx| {
Svg::new(
cx,
icons.map(|icons| match icons {
SpinboxIcons::PlusMinus => ICON_MINUS,
SpinboxIcons::Chevrons => ICON_CHEVRON_DOWN,
}),
)
})
.on_press(|ex| ex.emit(SpinboxEvent::Decrement))
.disabled(at_min)
.navigable(false)
.name(Localized::new("decrement"))
.variant(ButtonVariant::Text)
.class("spinbox-button");
}
});
})
.orientation(orientation)
.navigable(false)
}
fn clamp_value(&self, value: f64) -> f64 {
let value = if let Some(min) = self.min.get() { value.max(min) } else { value };
if let Some(max) = self.max.get() { value.min(max) } else { value }
}
fn emit_change(&self, cx: &mut EventContext, value: f64) {
if let Some(callback) = &self.on_change {
(callback)(cx, self.clamp_value(value));
}
}
}
impl Handle<'_, Spinbox> {
pub fn on_change<F>(self, callback: F) -> Self
where
F: 'static + Fn(&mut EventContext, f64),
{
self.modify(|spinbox| spinbox.on_change = Some(Box::new(callback)))
}
pub fn on_increment<F>(self, callback: F) -> Self
where
F: 'static + Fn(&mut EventContext) + Send + Sync,
{
self.modify(|spinbox: &mut Spinbox| spinbox.on_increment = Some(Box::new(callback)))
}
pub fn on_decrement<F>(self, callback: F) -> Self
where
F: 'static + Fn(&mut EventContext) + Send + Sync,
{
self.modify(|spinbox: &mut Spinbox| spinbox.on_decrement = Some(Box::new(callback)))
}
pub fn vertical<U: Into<bool> + Clone + 'static>(
self,
vertical: impl Res<U> + 'static,
) -> Self {
let vertical = vertical.to_signal(self.cx);
self.bind(vertical, move |handle| {
let vertical = vertical.get().into();
let orientation =
if vertical { Orientation::Vertical } else { Orientation::Horizontal };
handle.modify(move |spinbox| spinbox.orientation.set(orientation));
})
}
pub fn icons(self, icons: impl Res<SpinboxIcons> + 'static) -> Self {
let icons = icons.to_signal(self.cx);
self.bind(icons, move |handle| {
let icons = icons.get();
handle.modify(move |spinbox| spinbox.icons.set(icons));
})
}
pub fn min<U: Into<f64> + Clone + 'static>(self, min: impl Res<U> + 'static) -> Self {
let min_signal = min.to_signal(self.cx);
self.bind(min_signal, move |handle| {
let val: f64 = min_signal.get().into();
handle.modify(move |spinbox| spinbox.min.set(Some(val)));
})
}
pub fn max<U: Into<f64> + Clone + 'static>(self, max: impl Res<U> + 'static) -> Self {
let max_signal = max.to_signal(self.cx);
self.bind(max_signal, move |handle| {
let val: f64 = max_signal.get().into();
handle.modify(move |spinbox| spinbox.max.set(Some(val)));
})
}
}
impl View for Spinbox {
fn element(&self) -> Option<&'static str> {
Some("spinbox")
}
fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
event.map(|spinbox_event, _| match spinbox_event {
SpinboxEvent::Increment => {
if self.on_change.is_some() {
self.emit_change(cx, self.value.get() + 1.0);
}
if let Some(callback) = &self.on_increment {
(callback)(cx)
}
}
SpinboxEvent::Decrement => {
if self.on_change.is_some() {
self.emit_change(cx, self.value.get() - 1.0);
}
if let Some(callback) = &self.on_decrement {
(callback)(cx)
}
}
SpinboxEvent::SetMin => {
if let Some(min) = self.min.get() {
self.emit_change(cx, min);
}
}
SpinboxEvent::SetMax => {
if let Some(max) = self.max.get() {
self.emit_change(cx, max);
}
}
});
}
}