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}