1use crate::{
2 border::BorderBuilder,
3 brush::Brush,
4 button::{ButtonBuilder, ButtonMessage},
5 core::{
6 color::Color,
7 num_traits::NumCast,
8 num_traits::NumOps,
9 num_traits::{clamp, Bounded, NumAssign},
10 pool::Handle,
11 },
12 decorator::DecoratorBuilder,
13 define_constructor,
14 formatted_text::WrapMode,
15 grid::{Column, GridBuilder, Row},
16 message::{KeyCode, MessageDirection, UiMessage},
17 text_box::{TextBox, TextBoxBuilder, TextBoxMessage},
18 utils::{make_arrow, ArrowDirection},
19 widget::{Widget, WidgetBuilder, WidgetMessage},
20 BuildContext, Control, HorizontalAlignment, NodeHandleMapping, Thickness, UiNode,
21 UserInterface, VerticalAlignment, BRUSH_DARK, BRUSH_LIGHT,
22};
23use std::{
24 any::{Any, TypeId},
25 fmt::{Debug, Display},
26 ops::{Deref, DerefMut},
27 str::FromStr,
28};
29
30pub trait NumericType:
31 NumAssign
32 + FromStr
33 + Clone
34 + Copy
35 + NumOps
36 + PartialOrd
37 + Display
38 + Bounded
39 + Debug
40 + Send
41 + Sync
42 + NumCast
43 + Default
44 + 'static
45{
46}
47
48impl<T> NumericType for T where
49 T: NumAssign
50 + FromStr
51 + Clone
52 + Copy
53 + NumOps
54 + PartialOrd
55 + Bounded
56 + Display
57 + Debug
58 + Send
59 + Sync
60 + NumCast
61 + Default
62 + 'static
63{
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub enum NumericUpDownMessage<T: NumericType> {
68 Value(T),
69}
70
71impl<T: NumericType> NumericUpDownMessage<T> {
72 define_constructor!(NumericUpDownMessage:Value => fn value(T), layout: false);
73}
74
75#[derive(Clone)]
76pub struct NumericUpDown<T: NumericType> {
77 widget: Widget,
78 field: Handle<UiNode>,
79 increase: Handle<UiNode>,
80 decrease: Handle<UiNode>,
81 value: T,
82 step: T,
83 min_value: T,
84 max_value: T,
85 precision: usize,
86}
87
88impl<T: NumericType> Deref for NumericUpDown<T> {
89 type Target = Widget;
90
91 fn deref(&self) -> &Self::Target {
92 &self.widget
93 }
94}
95
96impl<T: NumericType> DerefMut for NumericUpDown<T> {
97 fn deref_mut(&mut self) -> &mut Self::Target {
98 &mut self.widget
99 }
100}
101
102impl<T: NumericType> NumericUpDown<T> {
103 fn clamp_value(&self, value: T) -> T {
104 clamp(value, self.min_value, self.max_value)
105 }
106
107 fn try_parse_value(&mut self, ui: &mut UserInterface) {
108 if let Some(field) = ui.node(self.field).cast::<TextBox>() {
110 if let Ok(value) = field.text().parse::<T>() {
111 let value = self.clamp_value(value);
112 ui.send_message(NumericUpDownMessage::value(
113 self.handle(),
114 MessageDirection::ToWidget,
115 value,
116 ));
117 }
118 }
119 }
120}
121
122fn saturating_sub<T: NumericType>(a: T, b: T) -> T {
123 if b <= a - T::min_value() {
124 a - b
125 } else {
126 T::min_value()
127 }
128}
129
130fn saturating_add<T: NumericType>(a: T, b: T) -> T {
131 if b <= T::max_value() - a {
132 a + b
133 } else {
134 T::max_value()
135 }
136}
137
138impl<T: NumericType> Control for NumericUpDown<T> {
139 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
140 if type_id == TypeId::of::<Self>() {
141 Some(self)
142 } else {
143 None
144 }
145 }
146
147 fn resolve(&mut self, node_map: &NodeHandleMapping) {
148 node_map.resolve(&mut self.field);
149 node_map.resolve(&mut self.increase);
150 node_map.resolve(&mut self.decrease);
151 }
152
153 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
154 self.widget.handle_routed_message(ui, message);
155
156 if let Some(msg) = message.data::<WidgetMessage>() {
157 if message.destination() == self.field {
158 match msg {
159 WidgetMessage::LostFocus => {
160 self.try_parse_value(ui);
161 }
162 WidgetMessage::KeyDown(KeyCode::Return) => {
163 self.try_parse_value(ui);
164 }
165 _ => {}
166 }
167 }
168 } else if let Some(NumericUpDownMessage::Value(value)) =
169 message.data::<NumericUpDownMessage<T>>()
170 {
171 if message.direction() == MessageDirection::ToWidget
172 && message.destination() == self.handle()
173 {
174 let clamped = self.clamp_value(*value);
175 if self.value != clamped {
176 self.value = clamped;
177
178 ui.send_message(TextBoxMessage::text(
180 self.field,
181 MessageDirection::ToWidget,
182 format!("{:.1$}", self.value, self.precision),
183 ));
184
185 let mut msg = NumericUpDownMessage::value(
186 self.handle,
187 MessageDirection::FromWidget,
188 self.value,
189 );
190 msg.set_handled(message.handled());
192 msg.flags = message.flags;
193 ui.send_message(msg);
194 }
195 }
196 } else if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
197 if message.destination() == self.decrease {
198 let value = self.clamp_value(saturating_sub(self.value, self.step));
199 ui.send_message(NumericUpDownMessage::value(
200 self.handle(),
201 MessageDirection::ToWidget,
202 value,
203 ));
204 } else if message.destination() == self.increase {
205 let value = self.clamp_value(saturating_add(self.value, self.step));
206
207 ui.send_message(NumericUpDownMessage::value(
208 self.handle(),
209 MessageDirection::ToWidget,
210 value,
211 ));
212 }
213 }
214 }
215}
216
217pub struct NumericUpDownBuilder<T: NumericType> {
218 widget_builder: WidgetBuilder,
219 value: T,
220 step: T,
221 min_value: T,
222 max_value: T,
223 precision: usize,
224}
225
226pub fn make_button(ctx: &mut BuildContext, arrow: ArrowDirection, row: usize) -> Handle<UiNode> {
227 ButtonBuilder::new(
228 WidgetBuilder::new()
229 .with_margin(Thickness::right(1.0))
230 .on_row(row),
231 )
232 .with_back(
233 DecoratorBuilder::new(BorderBuilder::new(
234 WidgetBuilder::new().with_foreground(Brush::Solid(Color::opaque(90, 90, 90))),
235 ))
236 .with_normal_brush(Brush::Solid(Color::opaque(60, 60, 60)))
237 .with_hover_brush(Brush::Solid(Color::opaque(80, 80, 80)))
238 .with_pressed_brush(Brush::Solid(Color::opaque(80, 118, 178)))
239 .build(ctx),
240 )
241 .with_content(make_arrow(ctx, arrow, 6.0))
242 .build(ctx)
243}
244
245impl<T: NumericType> NumericUpDownBuilder<T> {
246 pub fn new(widget_builder: WidgetBuilder) -> Self {
247 Self {
248 widget_builder,
249 value: T::zero(),
250 step: T::one(),
251 min_value: T::min_value(),
252 max_value: T::max_value(),
253 precision: 3,
254 }
255 }
256
257 fn set_value(&mut self, value: T) {
258 self.value = clamp(value, self.min_value, self.max_value);
259 }
260
261 pub fn with_min_value(mut self, value: T) -> Self {
262 self.min_value = value;
263 self.set_value(self.value);
264 self
265 }
266
267 pub fn with_max_value(mut self, value: T) -> Self {
268 self.max_value = value;
269 self.set_value(self.value);
270 self
271 }
272
273 pub fn with_value(mut self, value: T) -> Self {
274 self.value = value;
275 self.set_value(value);
276 self
277 }
278
279 pub fn with_step(mut self, step: T) -> Self {
280 self.step = step;
281 self
282 }
283
284 pub fn with_precision(mut self, precision: usize) -> Self {
285 self.precision = precision;
286 self
287 }
288
289 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
290 let increase;
291 let decrease;
292 let field;
293 let back = BorderBuilder::new(
294 WidgetBuilder::new()
295 .with_background(BRUSH_DARK)
296 .with_foreground(BRUSH_LIGHT),
297 )
298 .with_stroke_thickness(Thickness::uniform(1.0))
299 .build(ctx);
300
301 let grid = GridBuilder::new(
302 WidgetBuilder::new()
303 .with_child({
304 field = TextBoxBuilder::new(WidgetBuilder::new().on_row(0).on_column(0))
305 .with_vertical_text_alignment(VerticalAlignment::Center)
306 .with_horizontal_text_alignment(HorizontalAlignment::Left)
307 .with_wrap(WrapMode::Letter)
308 .with_text(format!("{:.1$}", self.value, self.precision))
309 .build(ctx);
310 field
311 })
312 .with_child(
313 GridBuilder::new(
314 WidgetBuilder::new()
315 .on_column(1)
316 .with_child({
317 increase = make_button(ctx, ArrowDirection::Top, 0);
318 increase
319 })
320 .with_child({
321 decrease = make_button(ctx, ArrowDirection::Bottom, 1);
322 decrease
323 }),
324 )
325 .add_column(Column::auto())
326 .add_row(Row::stretch())
327 .add_row(Row::stretch())
328 .build(ctx),
329 ),
330 )
331 .add_row(Row::stretch())
332 .add_column(Column::stretch())
333 .add_column(Column::auto())
334 .build(ctx);
335
336 ctx.link(grid, back);
337
338 let node = NumericUpDown {
339 widget: self.widget_builder.with_child(back).build(),
340 increase,
341 decrease,
342 field,
343 value: self.value,
344 step: self.step,
345 min_value: self.min_value,
346 max_value: self.max_value,
347 precision: self.precision,
348 };
349
350 ctx.add_node(UiNode::new(node))
351 }
352}