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