Skip to main content

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