fyrox_ui/
range.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Range editor is used to display and edit closed ranges like `0..1`. See [`Range`] docs for more info and usage
22//! examples.
23
24#![warn(missing_docs)]
25
26use crate::{
27    core::{pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*},
28    define_constructor,
29    grid::{Column, GridBuilder, Row},
30    message::{MessageDirection, UiMessage},
31    numeric::{NumericType, NumericUpDownBuilder, NumericUpDownMessage},
32    text::TextBuilder,
33    widget::{Widget, WidgetBuilder},
34    BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
35};
36
37use fyrox_core::variable::InheritableVariable;
38use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
39use std::ops::{Deref, DerefMut, Range};
40
41/// A set of messages, that can be used to modify/fetch the state of a [`RangeEditor`] widget instance.
42#[derive(Debug, PartialEq, Eq, Clone)]
43pub enum RangeEditorMessage<T>
44where
45    T: NumericType,
46{
47    /// A message, that is used to either modifying or fetching the value of a [`RangeEditor`] widget instance.
48    Value(Range<T>),
49}
50
51impl<T: NumericType> RangeEditorMessage<T> {
52    define_constructor!(
53        /// Creates [`RangeEditorMessage::Value`] message.
54        RangeEditorMessage:Value => fn value(Range<T>), layout: false
55    );
56}
57
58/// Range editor is used to display and edit closed ranges like `0..1`. The widget is generic over numeric type,
59/// so you can display and editor ranges of any type, such as `u32`, `f32`, `f64`, etc.
60///
61/// ## Example
62///
63/// You can create range editors using [`RangeEditorBuilder`], like so:
64///
65/// ```rust
66/// # use fyrox_ui::{
67/// #     core::pool::Handle, range::RangeEditorBuilder, widget::WidgetBuilder, BuildContext, UiNode,
68/// # };
69/// fn create_range_editor(ctx: &mut BuildContext) -> Handle<UiNode> {
70///     RangeEditorBuilder::new(WidgetBuilder::new())
71///         .with_value(0u32..100u32)
72///         .build(ctx)
73/// }
74/// ```
75///
76/// This example creates an editor for `Range<u32>` type with `0..100` value.
77///
78/// ## Value
79///
80/// To change current value of a range editor, use [`RangeEditorMessage::Value`] message:
81///
82/// ```rust
83/// # use fyrox_ui::{
84/// #     core::pool::Handle, message::MessageDirection, range::RangeEditorMessage, UiNode,
85/// #     UserInterface,
86/// # };
87/// fn change_value(range_editor: Handle<UiNode>, ui: &UserInterface) {
88///     ui.send_message(RangeEditorMessage::value(
89///         range_editor,
90///         MessageDirection::ToWidget,
91///         5u32..20u32,
92///     ))
93/// }
94/// ```
95///
96/// To "catch" the moment when the value has changed, use the same message, but check for [`MessageDirection::FromWidget`] direction
97/// on the message:
98///
99/// ```rust
100/// # use fyrox_ui::{
101/// #     core::pool::Handle,
102/// #     message::{MessageDirection, UiMessage},
103/// #     range::RangeEditorMessage,
104/// #     UiNode,
105/// # };
106/// #
107/// fn fetch_value(range_editor: Handle<UiNode>, message: &UiMessage) {
108///     if let Some(RangeEditorMessage::Value(range)) = message.data::<RangeEditorMessage<u32>>() {
109///         if message.destination() == range_editor
110///             && message.direction() == MessageDirection::FromWidget
111///         {
112///             println!("The new value is: {:?}", range)
113///         }
114///     }
115/// }
116/// ```
117///
118/// Be very careful about the type of the range when sending a message, you need to send a range of exact type, that match the type
119/// of your editor, otherwise the message have no effect. The same applied to fetching.
120#[derive(Default, Debug, Clone, Reflect, Visit, ComponentProvider)]
121#[reflect(derived_type = "UiNode")]
122pub struct RangeEditor<T>
123where
124    T: NumericType,
125{
126    /// Base widget of the range editor.
127    pub widget: Widget,
128    /// Current value of the range editor.
129    pub value: InheritableVariable<Range<T>>,
130    /// A handle to numeric field that is used to show/modify start value of current range.
131    pub start: InheritableVariable<Handle<UiNode>>,
132    /// A handle to numeric field that is used to show/modify end value of current range.
133    pub end: InheritableVariable<Handle<UiNode>>,
134}
135
136impl<T: NumericType> ConstructorProvider<UiNode, UserInterface> for RangeEditor<T> {
137    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
138        GraphNodeConstructor::new::<Self>()
139            .with_variant(
140                format!("Range Editor<{}>", std::any::type_name::<T>()),
141                |ui| {
142                    RangeEditorBuilder::<T>::new(WidgetBuilder::new().with_name("Range Editor"))
143                        .build(&mut ui.build_ctx())
144                        .into()
145                },
146            )
147            .with_group("Range")
148    }
149}
150
151impl<T> Deref for RangeEditor<T>
152where
153    T: NumericType,
154{
155    type Target = Widget;
156
157    fn deref(&self) -> &Self::Target {
158        &self.widget
159    }
160}
161
162impl<T> DerefMut for RangeEditor<T>
163where
164    T: NumericType,
165{
166    fn deref_mut(&mut self) -> &mut Self::Target {
167        &mut self.widget
168    }
169}
170
171const SYNC_FLAG: u64 = 1;
172
173impl<T> TypeUuidProvider for RangeEditor<T>
174where
175    T: NumericType,
176{
177    fn type_uuid() -> Uuid {
178        combine_uuids(
179            uuid!("0eb2948e-8485-490e-8719-18a0bb6fe275"),
180            T::type_uuid(),
181        )
182    }
183}
184
185impl<T> Control for RangeEditor<T>
186where
187    T: NumericType,
188{
189    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
190        self.widget.handle_routed_message(ui, message);
191
192        if message.direction() == MessageDirection::ToWidget && message.flags != SYNC_FLAG {
193            if let Some(RangeEditorMessage::Value(range)) = message.data::<RangeEditorMessage<T>>()
194            {
195                if message.destination() == self.handle && *self.value != *range {
196                    self.value.set_value_and_mark_modified(range.clone());
197
198                    ui.send_message(NumericUpDownMessage::value(
199                        *self.start,
200                        MessageDirection::ToWidget,
201                        range.start,
202                    ));
203                    ui.send_message(NumericUpDownMessage::value(
204                        *self.end,
205                        MessageDirection::ToWidget,
206                        range.end,
207                    ));
208
209                    ui.send_message(message.reverse());
210                }
211            } else if let Some(NumericUpDownMessage::Value(value)) =
212                message.data::<NumericUpDownMessage<T>>()
213            {
214                if message.destination() == *self.start {
215                    if *value < self.value.end {
216                        ui.send_message(RangeEditorMessage::value(
217                            self.handle,
218                            MessageDirection::ToWidget,
219                            Range {
220                                start: *value,
221                                end: self.value.end,
222                            },
223                        ));
224                    } else {
225                        let mut msg = NumericUpDownMessage::value(
226                            *self.start,
227                            MessageDirection::ToWidget,
228                            self.value.end,
229                        );
230                        msg.flags = SYNC_FLAG;
231                        ui.send_message(msg);
232                    }
233                } else if message.destination() == *self.end {
234                    if *value > self.value.start {
235                        ui.send_message(RangeEditorMessage::value(
236                            self.handle,
237                            MessageDirection::ToWidget,
238                            Range {
239                                start: self.value.start,
240                                end: *value,
241                            },
242                        ));
243                    } else {
244                        let mut msg = NumericUpDownMessage::value(
245                            *self.end,
246                            MessageDirection::ToWidget,
247                            self.value.start,
248                        );
249                        msg.flags = SYNC_FLAG;
250                        ui.send_message(msg);
251                    }
252                }
253            }
254        }
255    }
256}
257
258/// Range editor builder creates [`RangeEditor`] instances and adds them to the user interface.
259pub struct RangeEditorBuilder<T>
260where
261    T: NumericType,
262{
263    widget_builder: WidgetBuilder,
264    value: Range<T>,
265}
266
267impl<T> RangeEditorBuilder<T>
268where
269    T: NumericType,
270{
271    /// Creates new builder instance.
272    pub fn new(widget_builder: WidgetBuilder) -> Self {
273        Self {
274            widget_builder,
275            value: Range::default(),
276        }
277    }
278
279    /// Sets a desired value of the editor.
280    pub fn with_value(mut self, value: Range<T>) -> Self {
281        self.value = value;
282        self
283    }
284
285    /// Finished widget building and adds the new instance to the user interface.
286    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
287        let start = NumericUpDownBuilder::new(
288            WidgetBuilder::new()
289                .with_margin(Thickness::uniform(1.0))
290                .on_column(0),
291        )
292        .with_value(self.value.start)
293        .build(ctx);
294        let end = NumericUpDownBuilder::new(
295            WidgetBuilder::new()
296                .with_margin(Thickness::uniform(1.0))
297                .on_column(2),
298        )
299        .with_value(self.value.end)
300        .build(ctx);
301        let editor = RangeEditor {
302            widget: self
303                .widget_builder
304                .with_child(
305                    GridBuilder::new(
306                        WidgetBuilder::new()
307                            .with_child(start)
308                            .with_child(
309                                TextBuilder::new(
310                                    WidgetBuilder::new()
311                                        .on_column(1)
312                                        .with_margin(Thickness::uniform(1.0)),
313                                )
314                                .with_vertical_text_alignment(VerticalAlignment::Center)
315                                .with_text("..")
316                                .build(ctx),
317                            )
318                            .with_child(end),
319                    )
320                    .add_column(Column::stretch())
321                    .add_column(Column::strict(10.0))
322                    .add_column(Column::stretch())
323                    .add_row(Row::stretch())
324                    .build(ctx),
325                )
326                .build(ctx),
327            value: self.value.into(),
328            start: start.into(),
329            end: end.into(),
330        };
331
332        ctx.add_node(UiNode::new(editor))
333    }
334}
335
336#[cfg(test)]
337mod test {
338    use crate::range::RangeEditorBuilder;
339    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
340
341    #[test]
342    fn test_deletion() {
343        test_widget_deletion(|ctx| RangeEditorBuilder::<f32>::new(WidgetBuilder::new()).build(ctx));
344    }
345}