1#![warn(missing_docs)]
24
25use crate::style::StyledProperty;
26use crate::{
27    border::BorderBuilder,
28    core::{
29        pool::Handle, reflect::prelude::*, type_traits::prelude::*, variable::InheritableVariable,
30        visitor::prelude::*,
31    },
32    decorator::DecoratorBuilder,
33    define_constructor,
34    font::FontResource,
35    message::{KeyCode, MessageDirection, 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_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
43use std::{
44    cell::RefCell,
45    ops::{Deref, DerefMut},
46};
47
48#[derive(Debug, Clone, PartialEq)]
50pub enum ButtonMessage {
51    Click,
55    Content(ButtonContent),
57    RepeatInterval(f32),
60    RepeatClicksOnHold(bool),
62}
63
64impl ButtonMessage {
65    define_constructor!(
66        ButtonMessage:Click => fn click(), layout: false
68    );
69    define_constructor!(
70        ButtonMessage:Content => fn content(ButtonContent), layout: false
72    );
73    define_constructor!(
74        ButtonMessage:RepeatInterval => fn repeat_interval(f32), layout: false
76    );
77    define_constructor!(
78        ButtonMessage:RepeatClicksOnHold => fn repeat_clicks_on_hold(bool), layout: false
80    );
81}
82
83#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
117#[type_uuid(id = "2abcf12b-2f19-46da-b900-ae8890f7c9c6")]
118pub struct Button {
119    pub widget: Widget,
121    pub decorator: InheritableVariable<Handle<UiNode>>,
123    pub content: InheritableVariable<Handle<UiNode>>,
125    #[visit(optional)]
127    #[reflect(min_value = 0.0)]
128    pub repeat_interval: InheritableVariable<f32>,
129    #[visit(optional)]
131    #[reflect(hidden)]
132    pub repeat_timer: RefCell<Option<f32>>,
133    #[visit(optional)]
136    pub repeat_clicks_on_hold: InheritableVariable<bool>,
137}
138
139impl Button {
140    pub const CORNER_RADIUS: &'static str = "Button.CornerRadius";
142    pub const BORDER_THICKNESS: &'static str = "Button.BorderThickness";
144
145    pub fn style() -> Style {
147        Style::default()
148            .with(Self::CORNER_RADIUS, 4.0f32)
149            .with(Self::BORDER_THICKNESS, Thickness::uniform(1.0))
150    }
151}
152
153impl ConstructorProvider<UiNode, UserInterface> for Button {
154    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
155        GraphNodeConstructor::new::<Self>()
156            .with_variant("Button", |ui| {
157                ButtonBuilder::new(
158                    WidgetBuilder::new()
159                        .with_width(100.0)
160                        .with_height(20.0)
161                        .with_name("Button"),
162                )
163                .build(&mut ui.build_ctx())
164                .into()
165            })
166            .with_group("Input")
167    }
168}
169
170crate::define_widget_deref!(Button);
171
172impl Control for Button {
173    fn update(&mut self, dt: f32, ui: &mut UserInterface) {
174        let mut repeat_timer = self.repeat_timer.borrow_mut();
175        if let Some(repeat_timer) = &mut *repeat_timer {
176            *repeat_timer -= dt;
177            if *repeat_timer <= 0.0 {
178                ui.send_message(ButtonMessage::click(
179                    self.handle(),
180                    MessageDirection::FromWidget,
181                ));
182                *repeat_timer = *self.repeat_interval;
183            }
184        }
185    }
186
187    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
188        self.widget.handle_routed_message(ui, message);
189
190        if let Some(msg) = message.data::<WidgetMessage>() {
191            if message.destination() == self.handle()
192                || self.has_descendant(message.destination(), ui)
193            {
194                match msg {
195                    WidgetMessage::MouseDown { .. }
196                    | WidgetMessage::TouchStarted { .. }
197                    | WidgetMessage::TouchMoved { .. } => {
198                        ui.capture_mouse(self.handle);
199                        message.set_handled(true);
200                        if *self.repeat_clicks_on_hold {
201                            self.repeat_timer.replace(Some(*self.repeat_interval));
202                        }
203                    }
204                    WidgetMessage::MouseUp { .. } | WidgetMessage::TouchEnded { .. } => {
205                        ui.send_message(ButtonMessage::click(
206                            self.handle(),
207                            MessageDirection::FromWidget,
208                        ));
209                        ui.release_mouse_capture();
210                        message.set_handled(true);
211                        self.repeat_timer.replace(None);
212                    }
213                    WidgetMessage::KeyDown(key_code) => {
214                        if !message.handled()
215                            && (*key_code == KeyCode::Enter || *key_code == KeyCode::Space)
216                        {
217                            ui.send_message(ButtonMessage::click(
218                                self.handle,
219                                MessageDirection::FromWidget,
220                            ));
221                            message.set_handled(true);
222                        }
223                    }
224                    _ => (),
225                }
226            }
227        } else if let Some(msg) = message.data::<ButtonMessage>() {
228            if message.destination() == self.handle() {
229                match msg {
230                    ButtonMessage::Click => (),
231                    ButtonMessage::Content(content) => {
232                        if self.content.is_some() {
233                            ui.send_message(WidgetMessage::remove(
234                                *self.content,
235                                MessageDirection::ToWidget,
236                            ));
237                        }
238                        self.content
239                            .set_value_and_mark_modified(content.build(&mut ui.build_ctx()));
240                        ui.send_message(WidgetMessage::link(
241                            *self.content,
242                            MessageDirection::ToWidget,
243                            *self.decorator,
244                        ));
245                    }
246                    ButtonMessage::RepeatInterval(interval) => {
247                        if *self.repeat_interval != *interval
248                            && message.direction() == MessageDirection::ToWidget
249                        {
250                            *self.repeat_interval = *interval;
251                            ui.send_message(message.reverse());
252                        }
253                    }
254                    ButtonMessage::RepeatClicksOnHold(repeat_clicks) => {
255                        if *self.repeat_clicks_on_hold != *repeat_clicks
256                            && message.direction() == MessageDirection::ToWidget
257                        {
258                            *self.repeat_clicks_on_hold = *repeat_clicks;
259                            ui.send_message(message.reverse());
260                        }
261                    }
262                }
263            }
264        }
265    }
266}
267
268#[derive(Debug, Clone, PartialEq)]
272pub enum ButtonContent {
273    Text {
276        text: String,
278        font: Option<FontResource>,
280        size: Option<StyledProperty<f32>>,
282    },
283    Node(Handle<UiNode>),
286}
287
288impl ButtonContent {
289    pub fn text<S: AsRef<str>>(s: S) -> Self {
291        Self::Text {
292            text: s.as_ref().to_owned(),
293            font: None,
294            size: None,
295        }
296    }
297
298    pub fn text_with_font<S: AsRef<str>>(s: S, font: FontResource) -> Self {
300        Self::Text {
301            text: s.as_ref().to_owned(),
302            font: Some(font),
303            size: None,
304        }
305    }
306
307    pub fn text_with_font_size<S: AsRef<str>>(
309        s: S,
310        font: FontResource,
311        size: StyledProperty<f32>,
312    ) -> Self {
313        Self::Text {
314            text: s.as_ref().to_owned(),
315            font: Some(font),
316            size: Some(size),
317        }
318    }
319
320    pub fn node(node: Handle<UiNode>) -> Self {
322        Self::Node(node)
323    }
324
325    fn build(&self, ctx: &mut BuildContext) -> Handle<UiNode> {
326        match self {
327            Self::Text { text, font, size } => TextBuilder::new(WidgetBuilder::new())
328                .with_text(text)
329                .with_horizontal_text_alignment(HorizontalAlignment::Center)
330                .with_vertical_text_alignment(VerticalAlignment::Center)
331                .with_font(font.clone().unwrap_or_else(|| ctx.default_font()))
332                .with_font_size(
333                    size.clone()
334                        .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
335                )
336                .build(ctx),
337            Self::Node(node) => *node,
338        }
339    }
340}
341
342pub struct ButtonBuilder {
344    widget_builder: WidgetBuilder,
345    content: Option<ButtonContent>,
346    back: Option<Handle<UiNode>>,
347    repeat_interval: f32,
348    repeat_clicks_on_hold: bool,
349}
350
351impl ButtonBuilder {
352    pub fn new(widget_builder: WidgetBuilder) -> Self {
354        Self {
355            widget_builder,
356            content: None,
357            back: None,
358            repeat_interval: 0.1,
359            repeat_clicks_on_hold: false,
360        }
361    }
362
363    pub fn with_text(mut self, text: &str) -> Self {
365        self.content = Some(ButtonContent::text(text));
366        self
367    }
368
369    pub fn with_text_and_font(mut self, text: &str, font: FontResource) -> Self {
371        self.content = Some(ButtonContent::text_with_font(text, font));
372        self
373    }
374
375    pub fn with_text_and_font_size(
377        mut self,
378        text: &str,
379        font: FontResource,
380        size: StyledProperty<f32>,
381    ) -> Self {
382        self.content = Some(ButtonContent::text_with_font_size(text, font, size));
383        self
384    }
385
386    pub fn with_content(mut self, node: Handle<UiNode>) -> Self {
388        self.content = Some(ButtonContent::Node(node));
389        self
390    }
391
392    pub fn with_back(mut self, decorator: Handle<UiNode>) -> Self {
396        self.back = Some(decorator);
397        self
398    }
399
400    pub fn with_repeat_clicks_on_hold(mut self, repeat: bool) -> Self {
403        self.repeat_clicks_on_hold = repeat;
404        self
405    }
406
407    pub fn with_repeat_interval(mut self, interval: f32) -> Self {
409        self.repeat_interval = interval;
410        self
411    }
412
413    pub fn build_node(self, ctx: &mut BuildContext) -> UiNode {
415        let content = self.content.map(|c| c.build(ctx)).unwrap_or_default();
416        let back = self.back.unwrap_or_else(|| {
417            DecoratorBuilder::new(
418                BorderBuilder::new(
419                    WidgetBuilder::new()
420                        .with_foreground(ctx.style.property(Style::BRUSH_DARKER))
421                        .with_child(content),
422                )
423                .with_pad_by_corner_radius(false)
424                .with_corner_radius(ctx.style.property(Button::CORNER_RADIUS))
425                .with_stroke_thickness(ctx.style.property(Button::BORDER_THICKNESS)),
426            )
427            .with_normal_brush(ctx.style.property(Style::BRUSH_LIGHT))
428            .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHTER))
429            .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST))
430            .build(ctx)
431        });
432
433        if content.is_some() {
434            ctx.link(content, back);
435        }
436
437        UiNode::new(Button {
438            widget: self
439                .widget_builder
440                .with_accepts_input(true)
441                .with_need_update(true)
442                .with_child(back)
443                .build(ctx),
444            decorator: back.into(),
445            content: content.into(),
446            repeat_interval: self.repeat_interval.into(),
447            repeat_clicks_on_hold: self.repeat_clicks_on_hold.into(),
448            repeat_timer: Default::default(),
449        })
450    }
451
452    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
454        let node = self.build_node(ctx);
455        ctx.add_node(node)
456    }
457}
458
459#[cfg(test)]
460mod test {
461    use crate::button::ButtonBuilder;
462    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
463
464    #[test]
465    fn test_deletion() {
466        test_widget_deletion(|ctx| ButtonBuilder::new(WidgetBuilder::new()).build(ctx));
467    }
468}