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}