rg3d_ui/
numeric.rs

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        // Parse input only when focus is lost from text field.
109        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                    // Sync text field.
179                    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                    // We must maintain flags
191                    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}