Skip to main content

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