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};
31use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
32use std::ops::{Deref, DerefMut};
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}
40
41impl ThumbMessage {
42    define_constructor!(ThumbMessage:DragStarted => fn drag_started(position: Vector2<f32>), layout: false);
43    define_constructor!(ThumbMessage:DragDelta => fn drag_delta(offset: Vector2<f32>), layout: false);
44    define_constructor!(ThumbMessage:DragCompleted => fn drag_completed(position: Vector2<f32>), layout: false);
45}
46
47#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
48#[type_uuid(id = "71ad2ff4-6e9e-461d-b7c2-867bd4039684")]
49pub struct Thumb {
50    pub widget: Widget,
51    pub click_pos: Vector2<f32>,
52}
53
54impl ConstructorProvider<UiNode, UserInterface> for Thumb {
55    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
56        GraphNodeConstructor::new::<Self>()
57            .with_variant("Thumb", |ui| {
58                ThumbBuilder::new(WidgetBuilder::new().with_name("Thumb"))
59                    .build(&mut ui.build_ctx())
60                    .into()
61            })
62            .with_group("Input")
63    }
64}
65
66crate::define_widget_deref!(Thumb);
67
68impl Control for Thumb {
69    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
70        self.widget.handle_routed_message(ui, message);
71
72        if let Some(msg) = message.data::<WidgetMessage>() {
73            match msg {
74                WidgetMessage::MouseDown { pos, button } => {
75                    if !message.handled() && *button == MouseButton::Left {
76                        ui.capture_mouse(self.handle);
77                        message.set_handled(true);
78                        self.click_pos = *pos;
79                        ui.send_message(ThumbMessage::drag_started(
80                            self.handle,
81                            MessageDirection::FromWidget,
82                            self.actual_local_position(),
83                        ));
84                    }
85                }
86                WidgetMessage::MouseUp { button, .. } => {
87                    if ui.captured_node() == self.handle && *button == MouseButton::Left {
88                        ui.send_message(ThumbMessage::drag_completed(
89                            self.handle,
90                            MessageDirection::FromWidget,
91                            self.actual_local_position(),
92                        ));
93
94                        ui.release_mouse_capture();
95                    }
96                }
97                WidgetMessage::MouseMove { pos, state } => {
98                    if ui.captured_node() == self.handle && state.left == ButtonState::Pressed {
99                        ui.send_message(ThumbMessage::drag_delta(
100                            self.handle,
101                            MessageDirection::FromWidget,
102                            self.visual_transform()
103                                .try_inverse()
104                                .unwrap_or_default()
105                                .transform_vector(&(*pos - self.click_pos)),
106                        ));
107                    }
108                }
109                _ => (),
110            }
111        }
112    }
113}
114
115pub struct ThumbBuilder {
116    widget_builder: WidgetBuilder,
117}
118
119impl ThumbBuilder {
120    pub fn new(widget_builder: WidgetBuilder) -> Self {
121        Self { widget_builder }
122    }
123
124    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
125        let thumb = Thumb {
126            widget: self.widget_builder.build(ctx),
127            click_pos: Default::default(),
128        };
129        ctx.add_node(UiNode::new(thumb))
130    }
131}
132
133#[cfg(test)]
134mod test {
135    use crate::thumb::ThumbBuilder;
136    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
137
138    #[test]
139    fn test_deletion() {
140        test_widget_deletion(|ctx| ThumbBuilder::new(WidgetBuilder::new()).build(ctx));
141    }
142}