fyrox_ui/
toggle.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    border::BorderBuilder,
23    core::{pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*},
24    decorator::{DecoratorBuilder, DecoratorMessage},
25    define_constructor,
26    message::{MessageDirection, UiMessage},
27    style::{resource::StyleResourceExt, Style},
28    widget::{Widget, WidgetBuilder, WidgetMessage},
29    BuildContext, Control, Thickness, UiNode, UserInterface,
30};
31
32use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
33use std::ops::{Deref, DerefMut};
34
35#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
36#[reflect(derived_type = "UiNode")]
37#[type_uuid(id = "8d8f114d-7fc6-4d7e-8f57-cd4e39958c36")]
38pub struct ToggleButton {
39    pub widget: Widget,
40    pub decorator: Handle<UiNode>,
41    pub is_toggled: bool,
42    pub content: Handle<UiNode>,
43}
44
45/// Messages that can be emitted by [`ToggleButton`] widget (or can be sent to the widget).
46#[derive(Debug, Clone, PartialEq)]
47pub enum ToggleButtonMessage {
48    Toggled(bool),
49    Content(Handle<UiNode>),
50}
51
52impl ToggleButtonMessage {
53    define_constructor!(
54        ToggleButtonMessage:Toggled => fn toggled(bool), layout: false
55    );
56    define_constructor!(
57        ToggleButtonMessage:Content => fn content(Handle<UiNode>), layout: false
58    );
59}
60
61impl ToggleButton {
62    /// A name of style property, that defines corner radius of a toggle button.
63    pub const CORNER_RADIUS: &'static str = "ToggleButton.CornerRadius";
64    /// A name of style property, that defines border thickness of a toggle button.
65    pub const BORDER_THICKNESS: &'static str = "ToggleButton.BorderThickness";
66
67    /// Returns a style of the widget. This style contains only widget-specific properties.
68    pub fn style() -> Style {
69        Style::default()
70            .with(Self::CORNER_RADIUS, 4.0f32)
71            .with(Self::BORDER_THICKNESS, Thickness::uniform(1.0))
72    }
73}
74
75impl ConstructorProvider<UiNode, UserInterface> for ToggleButton {
76    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
77        GraphNodeConstructor::new::<Self>()
78            .with_variant("ToggleButton", |ui| {
79                ToggleButtonBuilder::new(WidgetBuilder::new().with_name("ToggleButton"))
80                    .build(&mut ui.build_ctx())
81                    .into()
82            })
83            .with_group("Input")
84    }
85}
86
87crate::define_widget_deref!(ToggleButton);
88
89impl Control for ToggleButton {
90    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
91        self.widget.handle_routed_message(ui, message);
92
93        if let Some(msg) = message.data::<WidgetMessage>() {
94            if (message.destination() == self.handle()
95                || self.has_descendant(message.destination(), ui))
96                && message.direction() == MessageDirection::FromWidget
97            {
98                match msg {
99                    WidgetMessage::MouseDown { .. } => {
100                        ui.capture_mouse(self.handle());
101                    }
102                    WidgetMessage::MouseUp { .. } => {
103                        if ui.captured_node() == self.handle() {
104                            let new_state = !self.is_toggled;
105
106                            ui.send_message(ToggleButtonMessage::toggled(
107                                self.handle(),
108                                MessageDirection::ToWidget,
109                                new_state,
110                            ));
111
112                            ui.release_mouse_capture();
113                        }
114                    }
115                    _ => {}
116                }
117            }
118        } else if let Some(msg) = message.data::<ToggleButtonMessage>() {
119            if message.destination() == self.handle()
120                && message.direction() == MessageDirection::ToWidget
121            {
122                match msg {
123                    ToggleButtonMessage::Toggled(value) => {
124                        if self.is_toggled != *value {
125                            self.is_toggled = *value;
126
127                            ui.send_message(DecoratorMessage::select(
128                                self.decorator,
129                                MessageDirection::ToWidget,
130                                self.is_toggled,
131                            ));
132
133                            ui.send_message(message.reverse());
134                        }
135                    }
136                    ToggleButtonMessage::Content(content) => {
137                        ui.send_message(WidgetMessage::remove(
138                            self.content,
139                            MessageDirection::ToWidget,
140                        ));
141                        ui.send_message(WidgetMessage::link(
142                            *content,
143                            MessageDirection::ToWidget,
144                            self.decorator,
145                        ));
146                    }
147                }
148            }
149        }
150    }
151}
152
153pub struct ToggleButtonBuilder {
154    widget_builder: WidgetBuilder,
155    is_toggled: bool,
156    content: Handle<UiNode>,
157}
158
159impl ToggleButtonBuilder {
160    pub fn new(widget_builder: WidgetBuilder) -> Self {
161        Self {
162            widget_builder,
163            is_toggled: false,
164            content: Default::default(),
165        }
166    }
167
168    pub fn with_toggled(mut self, is_toggled: bool) -> Self {
169        self.is_toggled = is_toggled;
170        self
171    }
172
173    pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
174        self.content = content;
175        self
176    }
177
178    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
179        let decorator = DecoratorBuilder::new(
180            BorderBuilder::new(WidgetBuilder::new().with_child(self.content))
181                .with_corner_radius(ctx.style.property(ToggleButton::CORNER_RADIUS))
182                .with_stroke_thickness(ctx.style.property(ToggleButton::BORDER_THICKNESS))
183                .with_pad_by_corner_radius(true),
184        )
185        .with_pressable(true)
186        .with_selected_brush(ctx.style.property(Style::BRUSH_BRIGHT_BLUE))
187        .with_selected(self.is_toggled)
188        .build(ctx);
189
190        let canvas = ToggleButton {
191            widget: self.widget_builder.with_child(decorator).build(ctx),
192            decorator,
193            is_toggled: self.is_toggled,
194            content: self.content,
195        };
196        ctx.add_node(UiNode::new(canvas))
197    }
198}
199
200#[cfg(test)]
201mod test {
202    use crate::{test::test_widget_deletion, toggle::ToggleButtonBuilder, widget::WidgetBuilder};
203
204    #[test]
205    fn test_deletion() {
206        test_widget_deletion(|ctx| ToggleButtonBuilder::new(WidgetBuilder::new()).build(ctx));
207    }
208}