vizia_core/views/
spinbox.rs

1use crate::icons::{
2    ICON_CHEVRON_DOWN, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT, ICON_CHEVRON_UP, ICON_MINUS,
3    ICON_PLUS,
4};
5use crate::prelude::*;
6
7pub(crate) enum SpinboxEvent {
8    Increment,
9    Decrement,
10}
11
12/// A view which represents a value which can be incremented or decremented.
13pub struct Spinbox {
14    orientation: Signal<Orientation>,
15    icons: Signal<SpinboxIcons>,
16
17    on_decrement: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
18    on_increment: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
19}
20
21/// And enum which represents the icons that can be used for the increment and decrement buttons of the [Spinbox].
22#[derive(Clone, Copy, Debug, PartialEq)]
23pub enum SpinboxIcons {
24    /// A plus icon for the increment button and a minus icon for the decrement button.
25    PlusMinus,
26    /// A right chevron for the increment button and a left chevron for the decrement button.
27    Chevrons,
28}
29
30impl_res_simple!(SpinboxIcons);
31
32impl Spinbox {
33    /// Creates a new [Spinbox] view.
34    pub fn new<L, T>(cx: &mut Context, signal: L) -> Handle<Spinbox>
35    where
36        L: Copy + SignalGet<T> + Res<T> + 'static,
37        T: Clone + ToStringLocalized,
38    {
39        Self::custom(cx, move |cx| Label::new(cx, signal))
40    }
41
42    /// Creates a custom [Spinbox] view with the given content to represent the value.
43    pub fn custom<F, V>(cx: &mut Context, content: F) -> Handle<Spinbox>
44    where
45        F: Fn(&mut Context) -> Handle<V>,
46        V: 'static + View,
47    {
48        let orientation = Signal::new(Orientation::Horizontal);
49        let icons = Signal::new(SpinboxIcons::Chevrons);
50
51        Self { orientation, icons, on_decrement: None, on_increment: None }
52            .build(cx, move |cx| {
53                Binding::new(cx, orientation, move |cx| match orientation.get() {
54                    Orientation::Horizontal => {
55                        Button::new(cx, |cx| {
56                            Svg::new(
57                                cx,
58                                icons.map(|icons| match icons {
59                                    SpinboxIcons::PlusMinus => ICON_MINUS,
60                                    SpinboxIcons::Chevrons => ICON_CHEVRON_LEFT,
61                                }),
62                            )
63                        })
64                        .on_press(|ex| ex.emit(SpinboxEvent::Decrement))
65                        .navigable(true)
66                        .class("spinbox-button");
67                    }
68
69                    Orientation::Vertical => {
70                        Button::new(cx, |cx| {
71                            Svg::new(
72                                cx,
73                                icons.map(|icons| match icons {
74                                    SpinboxIcons::PlusMinus => ICON_PLUS,
75                                    SpinboxIcons::Chevrons => ICON_CHEVRON_UP,
76                                }),
77                            )
78                        })
79                        .on_press(|ex| ex.emit(SpinboxEvent::Increment))
80                        .navigable(true)
81                        .class("spinbox-button");
82                    }
83                });
84                (content)(cx).class("spinbox-value");
85                Binding::new(cx, orientation, move |cx| match orientation.get() {
86                    Orientation::Horizontal => {
87                        Button::new(cx, |cx| {
88                            Svg::new(
89                                cx,
90                                icons.map(|icons| match icons {
91                                    SpinboxIcons::PlusMinus => ICON_PLUS,
92                                    SpinboxIcons::Chevrons => ICON_CHEVRON_RIGHT,
93                                }),
94                            )
95                        })
96                        .on_press(|ex| ex.emit(SpinboxEvent::Increment))
97                        .navigable(true)
98                        .class("spinbox-button");
99                    }
100
101                    Orientation::Vertical => {
102                        Button::new(cx, |cx| {
103                            Svg::new(
104                                cx,
105                                icons.map(|icons| match icons {
106                                    SpinboxIcons::PlusMinus => ICON_MINUS,
107                                    SpinboxIcons::Chevrons => ICON_CHEVRON_DOWN,
108                                }),
109                            )
110                        })
111                        .on_press(|ex| ex.emit(SpinboxEvent::Decrement))
112                        .navigable(true)
113                        .class("spinbox-button");
114                    }
115                });
116            })
117            .toggle_class("horizontal", orientation.map(|o| o == &Orientation::Horizontal))
118            .toggle_class("vertical", orientation.map(|o| o == &Orientation::Vertical))
119            .navigable(true)
120    }
121}
122
123impl Handle<'_, Spinbox> {
124    /// Sets the callback which is triggered when the [Spinbox] value is incremented.
125    pub fn on_increment<F>(self, callback: F) -> Self
126    where
127        F: 'static + Fn(&mut EventContext) + Send + Sync,
128    {
129        self.modify(|spinbox: &mut Spinbox| spinbox.on_increment = Some(Box::new(callback)))
130    }
131
132    /// Sets the callback which is triggered when the [Spinbox] value is decremented.
133    pub fn on_decrement<F>(self, callback: F) -> Self
134    where
135        F: 'static + Fn(&mut EventContext) + Send + Sync,
136    {
137        self.modify(|spinbox: &mut Spinbox| spinbox.on_decrement = Some(Box::new(callback)))
138    }
139
140    /// Sets the orientation of the [Spinbox].
141    pub fn orientation(self, orientation: impl Res<Orientation> + 'static) -> Self {
142        let orientation = orientation.to_signal(self.cx);
143        self.bind(orientation, move |handle| {
144            let orientation = orientation.get();
145            handle.modify(move |spinbox| spinbox.orientation.set(orientation));
146        })
147    }
148
149    /// Set the icons which should be used for the increment and decrement buttons of the [Spinbox]
150    pub fn icons(self, icons: impl Res<SpinboxIcons> + 'static) -> Self {
151        let icons = icons.to_signal(self.cx);
152        self.bind(icons, move |handle| {
153            let icons = icons.get();
154            handle.modify(move |spinbox| spinbox.icons.set(icons));
155        })
156    }
157}
158
159impl View for Spinbox {
160    fn element(&self) -> Option<&'static str> {
161        Some("spinbox")
162    }
163
164    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
165        event.map(|spinbox_event, _| match spinbox_event {
166            SpinboxEvent::Increment => {
167                if let Some(callback) = &self.on_increment {
168                    (callback)(cx)
169                }
170            }
171
172            SpinboxEvent::Decrement => {
173                if let Some(callback) = &self.on_decrement {
174                    (callback)(cx)
175                }
176            }
177        });
178    }
179}