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}