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}