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