fyrox_ui/
thumb.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
21use crate::{
22    core::{
23        algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
24        visitor::prelude::*,
25    },
26    define_constructor,
27    message::{ButtonState, MessageDirection, MouseButton, UiMessage},
28    widget::{Widget, WidgetBuilder, WidgetMessage},
29    BuildContext, Control, UiNode, UserInterface,
30};
31
32use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
33use std::ops::{Deref, DerefMut};
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum ThumbMessage {
37    DragStarted { position: Vector2<f32> },
38    DragDelta { offset: Vector2<f32> },
39    DragCompleted { position: Vector2<f32> },
40}
41
42impl ThumbMessage {
43    define_constructor!(ThumbMessage:DragStarted => fn drag_started(position: Vector2<f32>), layout: false);
44    define_constructor!(ThumbMessage:DragDelta => fn drag_delta(offset: Vector2<f32>), layout: false);
45    define_constructor!(ThumbMessage:DragCompleted => fn drag_completed(position: Vector2<f32>), layout: false);
46}
47
48/// A helper widget that is used to provide basic dragging interaction. The widget itself does not
49/// move when being dragged, instead it captures mouse input when clicked and calculates the
50/// difference between its current position and its initial position. The user is responsible for
51/// the actual movement of the thumb widget.
52///
53/// Typical use case of a thumb widget is a draggable widget such as indicator in a scroll bar,
54/// splitter in a docking manager's tile, etc.
55///
56/// ```rust
57/// use fyrox_ui::{
58///     border::BorderBuilder,
59///     brush::Brush,
60///     core::{color::Color, pool::Handle},
61///     decorator::DecoratorBuilder,
62///     message::{CursorIcon, MessageDirection, UiMessage},
63///     thumb::{ThumbBuilder, ThumbMessage},
64///     widget::WidgetBuilder,
65///     BuildContext, UiNode,
66/// };
67///
68/// fn create_thumb(ctx: &mut BuildContext) -> Handle<UiNode> {
69///     ThumbBuilder::new(
70///         WidgetBuilder::new().with_child(
71///             DecoratorBuilder::new(BorderBuilder::new(
72///                 WidgetBuilder::new()
73///                     .with_width(5.0)
74///                     .with_cursor(Some(CursorIcon::Grab))
75///                     .with_foreground(Brush::Solid(Color::opaque(0, 150, 0)).into()),
76///             ))
77///             .with_pressable(false)
78///             .with_selected(false)
79///             .with_normal_brush(Brush::Solid(Color::opaque(0, 150, 0)).into())
80///             .with_hover_brush(Brush::Solid(Color::opaque(0, 255, 0)).into())
81///             .build(ctx),
82///         ),
83///     )
84///     .build(ctx)
85/// }
86///
87/// fn process_thumb_messages(my_thumb: Handle<UiNode>, message: &UiMessage) {
88///     if message.destination() == my_thumb && message.direction() == MessageDirection::FromWidget
89///     {
90///         if let Some(msg) = message.data::<ThumbMessage>() {
91///             match msg {
92///                 ThumbMessage::DragStarted { position } => {
93///                     // Sent by a thumb when it just started dragging. The provided position
94///                     // expressed in local coordinates of the thumb.
95///                 }
96///                 ThumbMessage::DragDelta { offset } => {
97///                     // Sent by a thumb when it is being dragged. The provided offset
98///                     // expressed in local coordinates of the thumb.
99///                 }
100///                 ThumbMessage::DragCompleted { position } => {
101///                     // Sent by a thumb when it just stopped dragging. The provided position
102///                     // expressed in local coordinates of the thumb.
103///                 }
104///             }
105///         }
106///     }
107/// }
108/// ```
109///
110/// This example creates a new thumb widget 5px wide and shows how to use its messages to get
111/// information about the actual movement.
112#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
113#[reflect(derived_type = "UiNode")]
114#[type_uuid(id = "71ad2ff4-6e9e-461d-b7c2-867bd4039684")]
115pub struct Thumb {
116    pub widget: Widget,
117    pub click_pos: Vector2<f32>,
118}
119
120impl ConstructorProvider<UiNode, UserInterface> for Thumb {
121    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
122        GraphNodeConstructor::new::<Self>()
123            .with_variant("Thumb", |ui| {
124                ThumbBuilder::new(WidgetBuilder::new().with_name("Thumb"))
125                    .build(&mut ui.build_ctx())
126                    .into()
127            })
128            .with_group("Input")
129    }
130}
131
132crate::define_widget_deref!(Thumb);
133
134impl Control for Thumb {
135    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
136        self.widget.handle_routed_message(ui, message);
137
138        if let Some(msg) = message.data::<WidgetMessage>() {
139            match msg {
140                WidgetMessage::MouseDown { pos, button } => {
141                    if !message.handled() && *button == MouseButton::Left {
142                        ui.capture_mouse(self.handle);
143                        message.set_handled(true);
144                        self.click_pos = *pos;
145                        ui.send_message(ThumbMessage::drag_started(
146                            self.handle,
147                            MessageDirection::FromWidget,
148                            self.actual_local_position(),
149                        ));
150                    }
151                }
152                WidgetMessage::MouseUp { button, .. } => {
153                    if ui.captured_node() == self.handle && *button == MouseButton::Left {
154                        ui.send_message(ThumbMessage::drag_completed(
155                            self.handle,
156                            MessageDirection::FromWidget,
157                            self.actual_local_position(),
158                        ));
159
160                        ui.release_mouse_capture();
161                    }
162                }
163                WidgetMessage::MouseMove { pos, state } => {
164                    if ui.captured_node() == self.handle && state.left == ButtonState::Pressed {
165                        ui.send_message(ThumbMessage::drag_delta(
166                            self.handle,
167                            MessageDirection::FromWidget,
168                            self.visual_transform()
169                                .try_inverse()
170                                .unwrap_or_default()
171                                .transform_vector(&(*pos - self.click_pos)),
172                        ));
173                    }
174                }
175                _ => (),
176            }
177        }
178    }
179}
180
181pub struct ThumbBuilder {
182    widget_builder: WidgetBuilder,
183}
184
185impl ThumbBuilder {
186    pub fn new(widget_builder: WidgetBuilder) -> Self {
187        Self { widget_builder }
188    }
189
190    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
191        let thumb = Thumb {
192            widget: self.widget_builder.build(ctx),
193            click_pos: Default::default(),
194        };
195        ctx.add_node(UiNode::new(thumb))
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use crate::thumb::ThumbBuilder;
202    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
203
204    #[test]
205    fn test_deletion() {
206        test_widget_deletion(|ctx| ThumbBuilder::new(WidgetBuilder::new()).build(ctx));
207    }
208}