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};
36use fyrox_core::variable::InheritableVariable;
37use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
38use std::ops::{Deref, DerefMut, Range};
39
40/// A set of messages, that can be used to modify/fetch the state of a [`RangeEditor`] widget instance.
41#[derive(Debug, PartialEq, Eq, Clone)]
42pub enum RangeEditorMessage<T>
43where
44    T: NumericType,
45{
46    /// A message, that is used to either modifying or fetching the value of a [`RangeEditor`] widget instance.
47    Value(Range<T>),
48}
49
50impl<T: NumericType> RangeEditorMessage<T> {
51    define_constructor!(
52        /// Creates [`RangeEditorMessage::Value`] message.
53        RangeEditorMessage:Value => fn value(Range<T>), layout: false
54    );
55}
56
57/// Range editor is used to display and edit closed ranges like `0..1`. The widget is generic over numeric type,
58/// so you can display and editor ranges of any type, such as `u32`, `f32`, `f64`, etc.
59///
60/// ## Example
61///
62/// You can create range editors using [`RangeEditorBuilder`], like so:
63///
64/// ```rust
65/// # use fyrox_ui::{
66/// #     core::pool::Handle, range::RangeEditorBuilder, widget::WidgetBuilder, BuildContext, UiNode,
67/// # };
68/// fn create_range_editor(ctx: &mut BuildContext) -> Handle<UiNode> {
69///     RangeEditorBuilder::new(WidgetBuilder::new())
70///         .with_value(0u32..100u32)
71///         .build(ctx)
72/// }
73/// ```
74///
75/// This example creates an editor for `Range<u32>` type with `0..100` value.
76///
77/// ## Value
78///
79/// To change current value of a range editor, use [`RangeEditorMessage::Value`] message:
80///
81/// ```rust
82/// # use fyrox_ui::{
83/// #     core::pool::Handle, message::MessageDirection, range::RangeEditorMessage, UiNode,
84/// #     UserInterface,
85/// # };
86/// fn change_value(range_editor: Handle<UiNode>, ui: &UserInterface) {
87///     ui.send_message(RangeEditorMessage::value(
88///         range_editor,
89///         MessageDirection::ToWidget,
90///         5u32..20u32,
91///     ))
92/// }
93/// ```
94///
95/// To "catch" the moment when the value has changed, use the same message, but check for [`MessageDirection::FromWidget`] direction
96/// on the message:
97///
98/// ```rust
99/// # use fyrox_ui::{
100/// #     core::pool::Handle,
101/// #     message::{MessageDirection, UiMessage},
102/// #     range::RangeEditorMessage,
103/// #     UiNode,
104/// # };
105/// #
106/// fn fetch_value(range_editor: Handle<UiNode>, message: &UiMessage) {
107///     if let Some(RangeEditorMessage::Value(range)) = message.data::<RangeEditorMessage<u32>>() {
108///         if message.destination() == range_editor
109///             && message.direction() == MessageDirection::FromWidget
110///         {
111///             println!("The new value is: {:?}", range)
112///         }
113///     }
114/// }
115/// ```
116///
117/// 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
118/// of your editor, otherwise the message have no effect. The same applied to fetching.
119#[derive(Default, Debug, Clone, Reflect, Visit, ComponentProvider)]
120pub struct RangeEditor<T>
121where
122    T: NumericType,
123{
124    /// Base widget of the range editor.
125    pub widget: Widget,
126    /// Current value of the range editor.
127    pub value: InheritableVariable<Range<T>>,
128    /// A handle to numeric field that is used to show/modify start value of current range.
129    pub start: InheritableVariable<Handle<UiNode>>,
130    /// A handle to numeric field that is used to show/modify end value of current range.
131    pub end: InheritableVariable<Handle<UiNode>>,
132}
133
134impl<T: NumericType> ConstructorProvider<UiNode, UserInterface> for RangeEditor<T> {
135    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
136        GraphNodeConstructor::new::<Self>()
137            .with_variant(
138                format!("Range Editor<{}>", std::any::type_name::<T>()),
139                |ui| {
140                    RangeEditorBuilder::<T>::new(WidgetBuilder::new().with_name("Range Editor"))
141                        .build(&mut ui.build_ctx())
142                        .into()
143                },
144            )
145            .with_group("Range")
146    }
147}
148
149impl<T> Deref for RangeEditor<T>
150where
151    T: NumericType,
152{
153    type Target = Widget;
154
155    fn deref(&self) -> &Self::Target {
156        &self.widget
157    }
158}
159
160impl<T> DerefMut for RangeEditor<T>
161where
162    T: NumericType,
163{
164    fn deref_mut(&mut self) -> &mut Self::Target {
165        &mut self.widget
166    }
167}
168
169const SYNC_FLAG: u64 = 1;
170
171impl<T> TypeUuidProvider for RangeEditor<T>
172where
173    T: NumericType,
174{
175    fn type_uuid() -> Uuid {
176        combine_uuids(
177            uuid!("0eb2948e-8485-490e-8719-18a0bb6fe275"),
178            T::type_uuid(),
179        )
180    }
181}
182
183impl<T> Control for RangeEditor<T>
184where
185    T: NumericType,
186{
187    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
188        self.widget.handle_routed_message(ui, message);
189
190        if message.direction() == MessageDirection::ToWidget && message.flags != SYNC_FLAG {
191            if let Some(RangeEditorMessage::Value(range)) = message.data::<RangeEditorMessage<T>>()
192            {
193                if message.destination() == self.handle && *self.value != *range {
194                    self.value.set_value_and_mark_modified(range.clone());
195
196                    ui.send_message(NumericUpDownMessage::value(
197                        *self.start,
198                        MessageDirection::ToWidget,
199                        range.start,
200                    ));
201                    ui.send_message(NumericUpDownMessage::value(
202                        *self.end,
203                        MessageDirection::ToWidget,
204                        range.end,
205                    ));
206
207                    ui.send_message(message.reverse());
208                }
209            } else if let Some(NumericUpDownMessage::Value(value)) =
210                message.data::<NumericUpDownMessage<T>>()
211            {
212                if message.destination() == *self.start {
213                    if *value < self.value.end {
214                        ui.send_message(RangeEditorMessage::value(
215                            self.handle,
216                            MessageDirection::ToWidget,
217                            Range {
218                                start: *value,
219                                end: self.value.end,
220                            },
221                        ));
222                    } else {
223                        let mut msg = NumericUpDownMessage::value(
224                            *self.start,
225                            MessageDirection::ToWidget,
226                            self.value.end,
227                        );
228                        msg.flags = SYNC_FLAG;
229                        ui.send_message(msg);
230                    }
231                } else if message.destination() == *self.end {
232                    if *value > self.value.start {
233                        ui.send_message(RangeEditorMessage::value(
234                            self.handle,
235                            MessageDirection::ToWidget,
236                            Range {
237                                start: self.value.start,
238                                end: *value,
239                            },
240                        ));
241                    } else {
242                        let mut msg = NumericUpDownMessage::value(
243                            *self.end,
244                            MessageDirection::ToWidget,
245                            self.value.start,
246                        );
247                        msg.flags = SYNC_FLAG;
248                        ui.send_message(msg);
249                    }
250                }
251            }
252        }
253    }
254}
255
256/// Range editor builder creates [`RangeEditor`] instances and adds them to the user interface.
257pub struct RangeEditorBuilder<T>
258where
259    T: NumericType,
260{
261    widget_builder: WidgetBuilder,
262    value: Range<T>,
263}
264
265impl<T> RangeEditorBuilder<T>
266where
267    T: NumericType,
268{
269    /// Creates new builder instance.
270    pub fn new(widget_builder: WidgetBuilder) -> Self {
271        Self {
272            widget_builder,
273            value: Range::default(),
274        }
275    }
276
277    /// Sets a desired value of the editor.
278    pub fn with_value(mut self, value: Range<T>) -> Self {
279        self.value = value;
280        self
281    }
282
283    /// Finished widget building and adds the new instance to the user interface.
284    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
285        let start = NumericUpDownBuilder::new(
286            WidgetBuilder::new()
287                .with_margin(Thickness::uniform(1.0))
288                .on_column(0),
289        )
290        .with_value(self.value.start)
291        .build(ctx);
292        let end = NumericUpDownBuilder::new(
293            WidgetBuilder::new()
294                .with_margin(Thickness::uniform(1.0))
295                .on_column(2),
296        )
297        .with_value(self.value.end)
298        .build(ctx);
299        let editor = RangeEditor {
300            widget: self
301                .widget_builder
302                .with_child(
303                    GridBuilder::new(
304                        WidgetBuilder::new()
305                            .with_child(start)
306                            .with_child(
307                                TextBuilder::new(
308                                    WidgetBuilder::new()
309                                        .on_column(1)
310                                        .with_margin(Thickness::uniform(1.0)),
311                                )
312                                .with_vertical_text_alignment(VerticalAlignment::Center)
313                                .with_text("..")
314                                .build(ctx),
315                            )
316                            .with_child(end),
317                    )
318                    .add_column(Column::stretch())
319                    .add_column(Column::strict(10.0))
320                    .add_column(Column::stretch())
321                    .add_row(Row::stretch())
322                    .build(ctx),
323                )
324                .build(ctx),
325            value: self.value.into(),
326            start: start.into(),
327            end: end.into(),
328        };
329
330        ctx.add_node(UiNode::new(editor))
331    }
332}
333
334#[cfg(test)]
335mod test {
336    use crate::range::RangeEditorBuilder;
337    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
338
339    #[test]
340    fn test_deletion() {
341        test_widget_deletion(|ctx| RangeEditorBuilder::<f32>::new(WidgetBuilder::new()).build(ctx));
342    }
343}