rg3d_ui/
button.rs

1use crate::{
2    border::BorderBuilder,
3    brush::{Brush, GradientPoint},
4    core::{algebra::Vector2, pool::Handle},
5    decorator::DecoratorBuilder,
6    define_constructor,
7    message::{MessageDirection, UiMessage},
8    text::TextBuilder,
9    ttf::SharedFont,
10    widget::{Widget, WidgetBuilder, WidgetMessage},
11    BuildContext, Control, HorizontalAlignment, NodeHandleMapping, Thickness, UiNode,
12    UserInterface, VerticalAlignment, BRUSH_LIGHT, BRUSH_LIGHTER, BRUSH_LIGHTEST, COLOR_DARKEST,
13    COLOR_LIGHTEST,
14};
15use std::{
16    any::{Any, TypeId},
17    ops::{Deref, DerefMut},
18};
19
20#[derive(Debug, Clone, PartialEq)]
21pub enum ButtonMessage {
22    Click,
23    Content(Handle<UiNode>),
24}
25
26impl ButtonMessage {
27    define_constructor!(ButtonMessage:Click => fn click(), layout: false);
28    define_constructor!(ButtonMessage:Content => fn content(Handle<UiNode>), layout: false);
29}
30
31#[derive(Clone)]
32pub struct Button {
33    widget: Widget,
34    decorator: Handle<UiNode>,
35    content: Handle<UiNode>,
36}
37
38crate::define_widget_deref!(Button);
39
40impl Button {
41    pub fn new(widget: Widget, body: Handle<UiNode>, content: Handle<UiNode>) -> Self {
42        Self {
43            widget,
44            decorator: body,
45            content,
46        }
47    }
48
49    pub fn content(&self) -> Handle<UiNode> {
50        self.content
51    }
52
53    pub fn set_content(&mut self, content: Handle<UiNode>) -> &mut Self {
54        self.content = content;
55        self
56    }
57}
58
59impl Control for Button {
60    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
61        if type_id == TypeId::of::<Self>() {
62            Some(self)
63        } else {
64            None
65        }
66    }
67
68    fn resolve(&mut self, node_map: &NodeHandleMapping) {
69        node_map.resolve(&mut self.content);
70        node_map.resolve(&mut self.decorator);
71    }
72
73    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
74        self.widget.handle_routed_message(ui, message);
75
76        if let Some(msg) = message.data::<WidgetMessage>() {
77            if message.destination() == self.handle()
78                || self.has_descendant(message.destination(), ui)
79            {
80                match msg {
81                    WidgetMessage::MouseUp { .. } => {
82                        ui.send_message(ButtonMessage::click(
83                            self.handle(),
84                            MessageDirection::FromWidget,
85                        ));
86                        ui.release_mouse_capture();
87                        message.set_handled(true);
88                    }
89                    WidgetMessage::MouseDown { .. } => {
90                        ui.capture_mouse(message.destination());
91                        message.set_handled(true);
92                    }
93                    _ => (),
94                }
95            }
96        } else if let Some(msg) = message.data::<ButtonMessage>() {
97            if message.destination() == self.handle() {
98                match msg {
99                    ButtonMessage::Click => (),
100                    ButtonMessage::Content(content) => {
101                        if self.content.is_some() {
102                            ui.send_message(WidgetMessage::remove(
103                                self.content,
104                                MessageDirection::ToWidget,
105                            ));
106                        }
107                        self.content = *content;
108                        ui.send_message(WidgetMessage::link(
109                            self.content,
110                            MessageDirection::ToWidget,
111                            self.decorator,
112                        ));
113                    }
114                }
115            }
116        }
117    }
118}
119
120pub enum ButtonContent {
121    Text(String),
122    Node(Handle<UiNode>),
123}
124
125pub struct ButtonBuilder {
126    widget_builder: WidgetBuilder,
127    content: Option<ButtonContent>,
128    font: Option<SharedFont>,
129    back: Option<Handle<UiNode>>,
130}
131
132impl ButtonBuilder {
133    pub fn new(widget_builder: WidgetBuilder) -> Self {
134        Self {
135            widget_builder,
136            content: None,
137            font: None,
138            back: None,
139        }
140    }
141
142    pub fn with_text(mut self, text: &str) -> Self {
143        self.content = Some(ButtonContent::Text(text.to_owned()));
144        self
145    }
146
147    pub fn with_content(mut self, node: Handle<UiNode>) -> Self {
148        self.content = Some(ButtonContent::Node(node));
149        self
150    }
151
152    pub fn with_font(mut self, font: SharedFont) -> Self {
153        self.font = Some(font);
154        self
155    }
156
157    pub fn with_back(mut self, decorator: Handle<UiNode>) -> Self {
158        self.back = Some(decorator);
159        self
160    }
161
162    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
163        let content = if let Some(content) = self.content {
164            match content {
165                ButtonContent::Text(txt) => TextBuilder::new(WidgetBuilder::new())
166                    .with_text(txt.as_str())
167                    .with_opt_font(self.font)
168                    .with_horizontal_text_alignment(HorizontalAlignment::Center)
169                    .with_vertical_text_alignment(VerticalAlignment::Center)
170                    .build(ctx),
171                ButtonContent::Node(node) => node,
172            }
173        } else {
174            Handle::NONE
175        };
176
177        let back = self.back.unwrap_or_else(|| {
178            DecoratorBuilder::new(
179                BorderBuilder::new(
180                    WidgetBuilder::new()
181                        .with_foreground(Brush::LinearGradient {
182                            from: Vector2::new(0.5, 0.0),
183                            to: Vector2::new(0.5, 1.0),
184                            stops: vec![
185                                GradientPoint {
186                                    stop: 0.0,
187                                    color: COLOR_LIGHTEST,
188                                },
189                                GradientPoint {
190                                    stop: 0.25,
191                                    color: COLOR_LIGHTEST,
192                                },
193                                GradientPoint {
194                                    stop: 1.0,
195                                    color: COLOR_DARKEST,
196                                },
197                            ],
198                        })
199                        .with_child(content),
200                )
201                .with_stroke_thickness(Thickness::uniform(1.0)),
202            )
203            .with_normal_brush(BRUSH_LIGHT)
204            .with_hover_brush(BRUSH_LIGHTER)
205            .with_pressed_brush(BRUSH_LIGHTEST)
206            .build(ctx)
207        });
208
209        if content.is_some() {
210            ctx.link(content, back);
211        }
212
213        let button = Button {
214            widget: self.widget_builder.with_child(back).build(),
215            decorator: back,
216            content,
217        };
218        ctx.add_node(UiNode::new(button))
219    }
220}