1#![warn(missing_docs)]
24
25use crate::message::MessageData;
26use crate::style::StyledProperty;
27use crate::{
28 border::BorderBuilder,
29 core::{
30 pool::Handle, reflect::prelude::*, type_traits::prelude::*, variable::InheritableVariable,
31 visitor::prelude::*,
32 },
33 decorator::DecoratorBuilder,
34 font::FontResource,
35 message::{KeyCode, UiMessage},
36 style::{resource::StyleResourceExt, Style},
37 text::TextBuilder,
38 widget::{Widget, WidgetBuilder, WidgetMessage},
39 BuildContext, Control, HorizontalAlignment, Thickness, UiNode, UserInterface,
40 VerticalAlignment,
41};
42use fyrox_core::pool::ObjectOrVariant;
43use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
44use std::cell::RefCell;
45
46#[derive(Debug, Clone, PartialEq)]
48pub enum ButtonMessage {
49 Click,
53 Content(ButtonContent),
55 RepeatInterval(f32),
58 RepeatClicksOnHold(bool),
60}
61impl MessageData for ButtonMessage {}
62
63#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
97#[type_uuid(id = "2abcf12b-2f19-46da-b900-ae8890f7c9c6")]
98#[reflect(derived_type = "UiNode")]
99pub struct Button {
100 pub widget: Widget,
102 pub decorator: InheritableVariable<Handle<UiNode>>,
104 pub content: InheritableVariable<Handle<UiNode>>,
106 #[visit(optional)]
108 #[reflect(min_value = 0.0)]
109 pub repeat_interval: InheritableVariable<f32>,
110 #[visit(optional)]
112 #[reflect(hidden)]
113 pub repeat_timer: RefCell<Option<f32>>,
114 #[visit(optional)]
117 pub repeat_clicks_on_hold: InheritableVariable<bool>,
118}
119
120impl Button {
121 pub const CORNER_RADIUS: &'static str = "Button.CornerRadius";
123 pub const BORDER_THICKNESS: &'static str = "Button.BorderThickness";
125
126 pub fn style() -> Style {
128 Style::default()
129 .with(Self::CORNER_RADIUS, 4.0f32)
130 .with(Self::BORDER_THICKNESS, Thickness::uniform(1.0))
131 }
132}
133
134impl ConstructorProvider<UiNode, UserInterface> for Button {
135 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
136 GraphNodeConstructor::new::<Self>()
137 .with_variant("Button", |ui| {
138 ButtonBuilder::new(
139 WidgetBuilder::new()
140 .with_width(100.0)
141 .with_height(20.0)
142 .with_name("Button"),
143 )
144 .build(&mut ui.build_ctx())
145 .to_base()
146 .into()
147 })
148 .with_group("Input")
149 }
150}
151
152crate::define_widget_deref!(Button);
153
154impl Control for Button {
155 fn update(&mut self, dt: f32, ui: &mut UserInterface) {
156 let mut repeat_timer = self.repeat_timer.borrow_mut();
157 if let Some(repeat_timer) = &mut *repeat_timer {
158 *repeat_timer -= dt;
159 if *repeat_timer <= 0.0 {
160 ui.post(self.handle(), ButtonMessage::Click);
161 *repeat_timer = *self.repeat_interval;
162 }
163 }
164 }
165
166 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
167 self.widget.handle_routed_message(ui, message);
168
169 if let Some(msg) = message.data::<WidgetMessage>() {
170 if message.destination() == self.handle()
171 || self.has_descendant(message.destination(), ui)
172 {
173 match msg {
174 WidgetMessage::MouseDown { .. }
175 | WidgetMessage::TouchStarted { .. }
176 | WidgetMessage::TouchMoved { .. } => {
177 ui.capture_mouse(message.destination());
181 message.set_handled(true);
182 if *self.repeat_clicks_on_hold {
183 self.repeat_timer.replace(Some(*self.repeat_interval));
184 }
185 }
186 WidgetMessage::MouseUp { .. } | WidgetMessage::TouchEnded { .. } => {
187 if self.screen_bounds().contains(ui.cursor_position()) && !message.handled()
191 {
192 ui.post(self.handle(), ButtonMessage::Click);
193 }
194 ui.release_mouse_capture();
195 message.set_handled(true);
196 self.repeat_timer.replace(None);
197 }
198 WidgetMessage::KeyDown(key_code) => {
199 if !message.handled()
200 && (*key_code == KeyCode::Enter || *key_code == KeyCode::Space)
201 {
202 ui.post(self.handle, ButtonMessage::Click);
203 message.set_handled(true);
204 }
205 }
206 _ => (),
207 }
208 }
209 } else if let Some(msg) = message.data_for::<ButtonMessage>(self.handle()) {
210 match msg {
211 ButtonMessage::Click => (),
212 ButtonMessage::Content(content) => {
213 if self.content.is_some() {
214 ui.send(*self.content, WidgetMessage::Remove);
215 }
216 self.content
217 .set_value_and_mark_modified(content.build(&mut ui.build_ctx()));
218 ui.send(*self.content, WidgetMessage::LinkWith(*self.decorator));
219 }
220 ButtonMessage::RepeatInterval(interval) => {
221 if *self.repeat_interval != *interval {
222 *self.repeat_interval = *interval;
223 ui.send_message(message.reverse());
224 }
225 }
226 ButtonMessage::RepeatClicksOnHold(repeat_clicks) => {
227 if *self.repeat_clicks_on_hold != *repeat_clicks {
228 *self.repeat_clicks_on_hold = *repeat_clicks;
229 ui.send_message(message.reverse());
230 }
231 }
232 }
233 }
234 }
235}
236
237#[derive(Debug, Clone, PartialEq)]
241pub enum ButtonContent {
242 Text {
245 text: String,
247 font: Option<FontResource>,
249 size: Option<StyledProperty<f32>>,
251 },
252 Node(Handle<UiNode>),
255}
256
257impl ButtonContent {
258 pub fn text<S: AsRef<str>>(s: S) -> Self {
260 Self::Text {
261 text: s.as_ref().to_owned(),
262 font: None,
263 size: None,
264 }
265 }
266
267 pub fn text_with_font<S: AsRef<str>>(s: S, font: FontResource) -> Self {
269 Self::Text {
270 text: s.as_ref().to_owned(),
271 font: Some(font),
272 size: None,
273 }
274 }
275
276 pub fn text_with_font_size<S: AsRef<str>>(
278 s: S,
279 font: FontResource,
280 size: StyledProperty<f32>,
281 ) -> Self {
282 Self::Text {
283 text: s.as_ref().to_owned(),
284 font: Some(font),
285 size: Some(size),
286 }
287 }
288
289 pub fn node(node: Handle<UiNode>) -> Self {
291 Self::Node(node)
292 }
293
294 fn build(&self, ctx: &mut BuildContext) -> Handle<UiNode> {
295 match self {
296 Self::Text { text, font, size } => TextBuilder::new(WidgetBuilder::new())
297 .with_text(text)
298 .with_horizontal_text_alignment(HorizontalAlignment::Center)
299 .with_vertical_text_alignment(VerticalAlignment::Center)
300 .with_font(font.clone().unwrap_or_else(|| ctx.default_font()))
301 .with_font_size(
302 size.clone()
303 .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
304 )
305 .build(ctx)
306 .to_base(),
307 Self::Node(node) => *node,
308 }
309 }
310}
311
312pub struct ButtonBuilder {
314 widget_builder: WidgetBuilder,
315 content: Option<ButtonContent>,
316 back: Option<Handle<UiNode>>,
317 repeat_interval: f32,
318 repeat_clicks_on_hold: bool,
319}
320
321fn make_decorator_builder(ctx: &mut BuildContext) -> DecoratorBuilder {
322 DecoratorBuilder::new(
323 BorderBuilder::new(WidgetBuilder::new())
324 .with_pad_by_corner_radius(false)
325 .with_corner_radius(ctx.style.property(Button::CORNER_RADIUS))
326 .with_stroke_thickness(ctx.style.property(Button::BORDER_THICKNESS)),
327 )
328}
329
330impl ButtonBuilder {
331 pub fn new(widget_builder: WidgetBuilder) -> Self {
333 Self {
334 widget_builder,
335 content: None,
336 back: None,
337 repeat_interval: 0.1,
338 repeat_clicks_on_hold: false,
339 }
340 }
341
342 pub fn with_text(mut self, text: &str) -> Self {
344 self.content = Some(ButtonContent::text(text));
345 self
346 }
347
348 pub fn with_text_and_font(mut self, text: &str, font: FontResource) -> Self {
350 self.content = Some(ButtonContent::text_with_font(text, font));
351 self
352 }
353
354 pub fn with_text_and_font_size(
356 mut self,
357 text: &str,
358 font: FontResource,
359 size: StyledProperty<f32>,
360 ) -> Self {
361 self.content = Some(ButtonContent::text_with_font_size(text, font, size));
362 self
363 }
364
365 pub fn with_content(mut self, node: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
367 self.content = Some(ButtonContent::Node(node.to_base()));
368 self
369 }
370
371 pub fn with_back(mut self, decorator: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
375 self.back = Some(decorator.to_base());
376 self
377 }
378
379 pub fn with_ok_back(mut self, ctx: &mut BuildContext) -> Self {
381 self.back = Some(
382 make_decorator_builder(ctx)
383 .with_ok_style(ctx)
384 .build(ctx)
385 .to_base(),
386 );
387 self
388 }
389
390 pub fn with_cancel_back(mut self, ctx: &mut BuildContext) -> Self {
392 self.back = Some(
393 make_decorator_builder(ctx)
394 .with_cancel_style(ctx)
395 .build(ctx)
396 .to_base(),
397 );
398 self
399 }
400
401 pub fn with_repeat_clicks_on_hold(mut self, repeat: bool) -> Self {
404 self.repeat_clicks_on_hold = repeat;
405 self
406 }
407
408 pub fn with_repeat_interval(mut self, interval: f32) -> Self {
410 self.repeat_interval = interval;
411 self
412 }
413
414 pub fn build_button(self, ctx: &mut BuildContext) -> Button {
416 let content = self.content.map(|c| c.build(ctx)).unwrap_or_default();
417 let back = self.back.unwrap_or_else(|| {
418 make_decorator_builder(ctx)
419 .with_normal_brush(ctx.style.property(Style::BRUSH_LIGHT))
420 .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHTER))
421 .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST))
422 .build(ctx)
423 .to_base()
424 });
425
426 if content.is_some() {
427 ctx.link(content, back);
428 }
429
430 Button {
431 widget: self
432 .widget_builder
433 .with_accepts_input(true)
434 .with_need_update(true)
435 .with_child(back)
436 .build(ctx),
437 decorator: back.into(),
438 content: content.into(),
439 repeat_interval: self.repeat_interval.into(),
440 repeat_clicks_on_hold: self.repeat_clicks_on_hold.into(),
441 repeat_timer: Default::default(),
442 }
443 }
444
445 pub fn build_node(self, ctx: &mut BuildContext) -> UiNode {
447 UiNode::new(self.build_button(ctx))
448 }
449
450 pub fn build(self, ctx: &mut BuildContext) -> Handle<Button> {
452 let node = self.build_button(ctx);
453 ctx.add(node)
454 }
455}
456
457#[cfg(test)]
458mod test {
459 use crate::button::ButtonBuilder;
460 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
461
462 #[test]
463 fn test_deletion() {
464 test_widget_deletion(|ctx| ButtonBuilder::new(WidgetBuilder::new()).build(ctx));
465 }
466}