Skip to main content

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    message::{MessageDirection, UiMessage},
26    style::{resource::StyleResourceExt, Style},
27    widget::{Widget, WidgetBuilder, WidgetMessage},
28    BuildContext, Control, Thickness, UiNode, UserInterface,
29};
30use fyrox_core::pool::ObjectOrVariant;
31
32use crate::message::MessageData;
33use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
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}
51impl MessageData for ToggleButtonMessage {}
52
53impl ToggleButton {
54    /// A name of style property, that defines corner radius of a toggle button.
55    pub const CORNER_RADIUS: &'static str = "ToggleButton.CornerRadius";
56    /// A name of style property, that defines border thickness of a toggle button.
57    pub const BORDER_THICKNESS: &'static str = "ToggleButton.BorderThickness";
58
59    /// Returns a style of the widget. This style contains only widget-specific properties.
60    pub fn style() -> Style {
61        Style::default()
62            .with(Self::CORNER_RADIUS, 4.0f32)
63            .with(Self::BORDER_THICKNESS, Thickness::uniform(1.0))
64    }
65}
66
67impl ConstructorProvider<UiNode, UserInterface> for ToggleButton {
68    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
69        GraphNodeConstructor::new::<Self>()
70            .with_variant("ToggleButton", |ui| {
71                ToggleButtonBuilder::new(WidgetBuilder::new().with_name("ToggleButton"))
72                    .build(&mut ui.build_ctx())
73                    .to_base()
74                    .into()
75            })
76            .with_group("Input")
77    }
78}
79
80crate::define_widget_deref!(ToggleButton);
81
82impl Control for ToggleButton {
83    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
84        self.widget.handle_routed_message(ui, message);
85
86        if let Some(msg) = message.data::<WidgetMessage>() {
87            if (message.destination() == self.handle()
88                || self.has_descendant(message.destination(), ui))
89                && message.direction() == MessageDirection::FromWidget
90            {
91                match msg {
92                    WidgetMessage::MouseDown { .. } => {
93                        ui.capture_mouse(self.handle());
94                    }
95                    WidgetMessage::MouseUp { .. } => {
96                        if ui.captured_node() == self.handle() {
97                            let new_state = !self.is_toggled;
98
99                            ui.send(self.handle(), ToggleButtonMessage::Toggled(new_state));
100
101                            ui.release_mouse_capture();
102                        }
103                    }
104                    _ => {}
105                }
106            }
107        } else if let Some(msg) = message.data_for::<ToggleButtonMessage>(self.handle()) {
108            match msg {
109                ToggleButtonMessage::Toggled(value) => {
110                    if self.is_toggled != *value {
111                        self.is_toggled = *value;
112                        ui.send(self.decorator, DecoratorMessage::Select(self.is_toggled));
113                        ui.send_message(message.reverse());
114                    }
115                }
116                ToggleButtonMessage::Content(content) => {
117                    ui.send(self.content, WidgetMessage::Remove);
118                    ui.send(*content, WidgetMessage::LinkWith(self.decorator));
119                }
120            }
121        }
122    }
123}
124
125pub struct ToggleButtonBuilder {
126    widget_builder: WidgetBuilder,
127    is_toggled: bool,
128    content: Handle<UiNode>,
129}
130
131impl ToggleButtonBuilder {
132    pub fn new(widget_builder: WidgetBuilder) -> Self {
133        Self {
134            widget_builder,
135            is_toggled: false,
136            content: Default::default(),
137        }
138    }
139
140    pub fn with_toggled(mut self, is_toggled: bool) -> Self {
141        self.is_toggled = is_toggled;
142        self
143    }
144
145    pub fn with_content(mut self, content: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
146        self.content = content.to_base();
147        self
148    }
149
150    pub fn build(self, ctx: &mut BuildContext) -> Handle<ToggleButton> {
151        let decorator = DecoratorBuilder::new(
152            BorderBuilder::new(WidgetBuilder::new().with_child(self.content))
153                .with_corner_radius(ctx.style.property(ToggleButton::CORNER_RADIUS))
154                .with_stroke_thickness(ctx.style.property(ToggleButton::BORDER_THICKNESS))
155                .with_pad_by_corner_radius(true),
156        )
157        .with_pressable(true)
158        .with_selected_brush(ctx.style.property(Style::BRUSH_BRIGHT_BLUE))
159        .with_selected(self.is_toggled)
160        .build(ctx)
161        .to_base();
162
163        let canvas = ToggleButton {
164            widget: self.widget_builder.with_child(decorator).build(ctx),
165            decorator,
166            is_toggled: self.is_toggled,
167            content: self.content,
168        };
169        ctx.add(canvas)
170    }
171}
172
173#[cfg(test)]
174mod test {
175    use crate::{test::test_widget_deletion, toggle::ToggleButtonBuilder, widget::WidgetBuilder};
176
177    #[test]
178    fn test_deletion() {
179        test_widget_deletion(|ctx| ToggleButtonBuilder::new(WidgetBuilder::new()).build(ctx));
180    }
181}