fyrox_ui/
decorator.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
21//! A visual element that is used to highlight standard states of interactive widgets. It has "pressed", "hover",
22//! "selected", "normal" appearances. See [`Decorator`] docs for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::style::resource::StyleResourceExt;
27use crate::style::{Style, StyledProperty};
28use crate::widget::WidgetBuilder;
29use crate::{
30    border::{Border, BorderBuilder},
31    brush::Brush,
32    core::{
33        algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
34        visitor::prelude::*,
35    },
36    define_constructor,
37    draw::DrawingContext,
38    message::{MessageDirection, UiMessage},
39    widget::{Widget, WidgetMessage},
40    BuildContext, Control, UiNode, UserInterface,
41};
42
43use fyrox_core::uuid_provider;
44use fyrox_core::variable::InheritableVariable;
45use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
46use std::ops::{Deref, DerefMut};
47
48/// A set of messages that is used to modify [`Decorator`] widgets state.
49#[derive(Debug, Clone, PartialEq)]
50pub enum DecoratorMessage {
51    /// This message is used to switch a decorator in a `Selected` state or not.
52    Select(bool),
53    /// Sets a new brush for `Hovered` state.
54    HoverBrush(StyledProperty<Brush>),
55    /// Sets a new brush for `Normal` state.
56    NormalBrush(StyledProperty<Brush>),
57    /// Sets a new brush for `Pressed` state.
58    PressedBrush(StyledProperty<Brush>),
59    /// Sets a new brush for `Selected` state.
60    SelectedBrush(StyledProperty<Brush>),
61}
62
63impl DecoratorMessage {
64    define_constructor!(
65        /// Creates a [`DecoratorMessage::Select`] message.
66        DecoratorMessage:Select => fn select(bool), layout: false
67    );
68    define_constructor!(
69        /// Creates a [`DecoratorMessage::HoverBrush`] message.
70        DecoratorMessage:HoverBrush => fn hover_brush(StyledProperty<Brush>), layout: false
71    );
72    define_constructor!(
73        /// Creates a [`DecoratorMessage::NormalBrush`] message.
74        DecoratorMessage:NormalBrush => fn normal_brush(StyledProperty<Brush>), layout: false
75    );
76    define_constructor!(
77        /// Creates a [`DecoratorMessage::PressedBrush`] message.
78        DecoratorMessage:PressedBrush => fn pressed_brush(StyledProperty<Brush>), layout: false
79    );
80    define_constructor!(
81        /// Creates a [`DecoratorMessage::SelectedBrush`] message.
82        DecoratorMessage:SelectedBrush => fn selected_brush(StyledProperty<Brush>), layout: false
83    );
84}
85
86/// A visual element that is used to highlight standard states of interactive widgets. It has "pressed", "hover",
87/// "selected", "normal" appearances (only one can be active at a time):
88///
89/// - `Pressed` - enables on mouse down message.
90/// - `Selected` - whether decorator selected or not.
91/// - `Hovered` - mouse is over decorator.
92/// - `Normal` - not selected, pressed, hovered.
93///
94/// This element is widely used to provide some generic visual behaviour for various widgets. For example it used
95/// to decorate buttons - it has use of three of these states. When it is clicked - the decorator will be in `Pressed`
96/// state, when hovered by a cursor - `Hovered`, otherwise it stays in `Normal` state.
97///
98/// ## Example
99///
100/// ```rust
101/// # use fyrox_ui::{
102/// #     border::BorderBuilder,
103/// #     brush::Brush,
104/// #     core::{color::Color, pool::Handle},
105/// #     decorator::DecoratorBuilder,
106/// #     widget::WidgetBuilder,
107/// #     BuildContext, UiNode,
108/// # };
109/// fn create_decorator(ctx: &mut BuildContext) -> Handle<UiNode> {
110///     DecoratorBuilder::new(BorderBuilder::new(WidgetBuilder::new()))
111///         .with_hover_brush(Brush::Solid(Color::opaque(0, 255, 0)).into())
112///         .build(ctx)
113/// }
114/// ```
115#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
116#[reflect(derived_type = "UiNode")]
117pub struct Decorator {
118    /// Base widget of the decorator.
119    #[component(include)]
120    pub border: Border,
121    /// Current brush used for `Normal` state.
122    pub normal_brush: InheritableVariable<StyledProperty<Brush>>,
123    /// Current brush used for `Hovered` state.
124    pub hover_brush: InheritableVariable<StyledProperty<Brush>>,
125    /// Current brush used for `Pressed` state.
126    pub pressed_brush: InheritableVariable<StyledProperty<Brush>>,
127    /// Current brush used for `Selected` state.
128    pub selected_brush: InheritableVariable<StyledProperty<Brush>>,
129    /// Whether the decorator is in `Selected` state or not.
130    pub is_selected: InheritableVariable<bool>,
131    /// Whether the decorator should react to mouse clicks and switch its state to `Pressed` or not.
132    pub is_pressable: InheritableVariable<bool>,
133}
134
135impl ConstructorProvider<UiNode, UserInterface> for Decorator {
136    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
137        GraphNodeConstructor::new::<Self>()
138            .with_variant("Decorator", |ui| {
139                DecoratorBuilder::new(BorderBuilder::new(
140                    WidgetBuilder::new().with_name("Decorator"),
141                ))
142                .build(&mut ui.build_ctx())
143                .into()
144            })
145            .with_group("Visual")
146    }
147}
148
149impl Deref for Decorator {
150    type Target = Widget;
151
152    fn deref(&self) -> &Self::Target {
153        &self.border
154    }
155}
156
157impl DerefMut for Decorator {
158    fn deref_mut(&mut self) -> &mut Self::Target {
159        &mut self.border
160    }
161}
162
163uuid_provider!(Decorator = "bb4b60aa-c657-4ed6-8db6-d7f374397c73");
164
165impl Control for Decorator {
166    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
167        self.border.measure_override(ui, available_size)
168    }
169
170    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
171        self.border.arrange_override(ui, final_size)
172    }
173
174    fn draw(&self, drawing_context: &mut DrawingContext) {
175        self.border.draw(drawing_context)
176    }
177
178    fn update(&mut self, dt: f32, ui: &mut UserInterface) {
179        self.border.update(dt, ui)
180    }
181
182    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
183        self.border.handle_routed_message(ui, message);
184
185        if let Some(msg) = message.data::<DecoratorMessage>() {
186            match msg {
187                &DecoratorMessage::Select(value) => {
188                    if *self.is_selected != value {
189                        self.is_selected.set_value_and_mark_modified(value);
190
191                        ui.send_message(WidgetMessage::background(
192                            self.handle(),
193                            MessageDirection::ToWidget,
194                            if *self.is_selected {
195                                (*self.selected_brush).clone()
196                            } else {
197                                (*self.normal_brush).clone()
198                            },
199                        ));
200                    }
201                }
202                DecoratorMessage::HoverBrush(brush) => {
203                    self.hover_brush.set_value_and_mark_modified(brush.clone());
204                    if self.has_descendant(ui.picked_node, ui) {
205                        ui.send_message(WidgetMessage::background(
206                            self.handle(),
207                            MessageDirection::ToWidget,
208                            (*self.hover_brush).clone(),
209                        ));
210                    }
211                }
212                DecoratorMessage::NormalBrush(brush) => {
213                    self.normal_brush.set_value_and_mark_modified(brush.clone());
214                    if !*self.is_selected && !self.has_descendant(ui.picked_node, ui) {
215                        ui.send_message(WidgetMessage::background(
216                            self.handle(),
217                            MessageDirection::ToWidget,
218                            (*self.normal_brush).clone(),
219                        ));
220                    }
221                }
222                DecoratorMessage::PressedBrush(brush) => {
223                    self.pressed_brush
224                        .set_value_and_mark_modified(brush.clone());
225                }
226                DecoratorMessage::SelectedBrush(brush) => {
227                    self.selected_brush
228                        .set_value_and_mark_modified(brush.clone());
229                    if *self.is_selected {
230                        ui.send_message(WidgetMessage::background(
231                            self.handle(),
232                            MessageDirection::ToWidget,
233                            (*self.selected_brush).clone(),
234                        ));
235                    }
236                }
237            }
238        } else if let Some(msg) = message.data::<WidgetMessage>() {
239            if message.destination() == self.handle()
240                || self.has_descendant(message.destination(), ui)
241            {
242                match msg {
243                    WidgetMessage::MouseLeave => {
244                        ui.send_message(WidgetMessage::background(
245                            self.handle(),
246                            MessageDirection::ToWidget,
247                            if *self.is_selected {
248                                (*self.selected_brush).clone()
249                            } else {
250                                (*self.normal_brush).clone()
251                            },
252                        ));
253                    }
254                    WidgetMessage::MouseEnter => {
255                        ui.send_message(WidgetMessage::background(
256                            self.handle(),
257                            MessageDirection::ToWidget,
258                            if *self.is_selected {
259                                (*self.selected_brush).clone()
260                            } else {
261                                (*self.hover_brush).clone()
262                            },
263                        ));
264                    }
265                    WidgetMessage::MouseDown { .. } if *self.is_pressable => {
266                        ui.send_message(WidgetMessage::background(
267                            self.handle(),
268                            MessageDirection::ToWidget,
269                            (*self.pressed_brush).clone(),
270                        ));
271                    }
272                    WidgetMessage::MouseUp { .. } => {
273                        if *self.is_selected {
274                            ui.send_message(WidgetMessage::background(
275                                self.handle(),
276                                MessageDirection::ToWidget,
277                                (*self.selected_brush).clone(),
278                            ));
279                        } else {
280                            ui.send_message(WidgetMessage::background(
281                                self.handle(),
282                                MessageDirection::ToWidget,
283                                (*self.normal_brush).clone(),
284                            ));
285                        }
286                    }
287                    WidgetMessage::ResetVisual => {
288                        self.is_selected.set_value_and_mark_modified(false);
289                        ui.send_message(WidgetMessage::background(
290                            self.handle(),
291                            MessageDirection::ToWidget,
292                            (*self.normal_brush).clone(),
293                        ));
294                    }
295                    _ => {}
296                }
297            }
298
299            if message.destination() == self.handle() {
300                if let WidgetMessage::Style(style) = msg {
301                    self.normal_brush.update(style);
302                    self.hover_brush.update(style);
303                    self.pressed_brush.update(style);
304                    self.selected_brush.update(style);
305                }
306            }
307        }
308    }
309}
310
311/// Creates [`Decorator`] widget instances and adds them to the user interface.
312pub struct DecoratorBuilder {
313    border_builder: BorderBuilder,
314    normal_brush: Option<StyledProperty<Brush>>,
315    hover_brush: Option<StyledProperty<Brush>>,
316    pressed_brush: Option<StyledProperty<Brush>>,
317    selected_brush: Option<StyledProperty<Brush>>,
318    pressable: bool,
319    selected: bool,
320}
321
322impl DecoratorBuilder {
323    /// Creates a new decorator builder.
324    pub fn new(border_builder: BorderBuilder) -> Self {
325        Self {
326            normal_brush: None,
327            hover_brush: None,
328            pressed_brush: None,
329            selected_brush: None,
330            pressable: true,
331            selected: false,
332            border_builder,
333        }
334    }
335
336    /// Sets a desired brush for `Normal` state.
337    pub fn with_normal_brush(mut self, brush: StyledProperty<Brush>) -> Self {
338        self.normal_brush = Some(brush);
339        self
340    }
341
342    /// Sets a desired brush for `Hovered` state.
343    pub fn with_hover_brush(mut self, brush: StyledProperty<Brush>) -> Self {
344        self.hover_brush = Some(brush);
345        self
346    }
347
348    /// Sets a desired brush for `Pressed` state.
349    pub fn with_pressed_brush(mut self, brush: StyledProperty<Brush>) -> Self {
350        self.pressed_brush = Some(brush);
351        self
352    }
353
354    /// Sets a desired brush for `Selected` state.
355    pub fn with_selected_brush(mut self, brush: StyledProperty<Brush>) -> Self {
356        self.selected_brush = Some(brush);
357        self
358    }
359
360    /// Sets whether the decorator is pressable or not.
361    pub fn with_pressable(mut self, pressable: bool) -> Self {
362        self.pressable = pressable;
363        self
364    }
365
366    /// Sets whether the decorator is selected or not.
367    pub fn with_selected(mut self, selected: bool) -> Self {
368        self.selected = selected;
369        self
370    }
371
372    /// Finishes decorator instance building.
373    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
374        let normal_brush = self
375            .normal_brush
376            .unwrap_or_else(|| ctx.style.property::<Brush>(Style::BRUSH_LIGHT));
377        let hover_brush = self
378            .hover_brush
379            .unwrap_or_else(|| ctx.style.property::<Brush>(Style::BRUSH_LIGHTER));
380        let pressed_brush = self
381            .pressed_brush
382            .unwrap_or_else(|| ctx.style.property::<Brush>(Style::BRUSH_LIGHTEST));
383        let selected_brush = self
384            .selected_brush
385            .unwrap_or_else(|| ctx.style.property::<Brush>(Style::BRUSH_BRIGHT));
386
387        if self.border_builder.widget_builder.foreground.is_none() {
388            let brush = ctx.style.property(Style::BRUSH_DARKER);
389            self.border_builder.widget_builder.foreground = Some(brush);
390        }
391
392        let mut border = self.border_builder.build_border(ctx);
393
394        if self.selected {
395            *border.background = selected_brush.clone();
396        } else {
397            *border.background = normal_brush.clone();
398        }
399
400        let node = UiNode::new(Decorator {
401            border,
402            normal_brush: normal_brush.into(),
403            hover_brush: hover_brush.into(),
404            pressed_brush: pressed_brush.into(),
405            selected_brush: selected_brush.into(),
406            is_selected: self.selected.into(),
407            is_pressable: self.pressable.into(),
408        });
409        ctx.add_node(node)
410    }
411}
412
413#[cfg(test)]
414mod test {
415    use crate::border::BorderBuilder;
416    use crate::decorator::DecoratorBuilder;
417    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
418
419    #[test]
420    fn test_deletion() {
421        test_widget_deletion(|ctx| {
422            DecoratorBuilder::new(BorderBuilder::new(WidgetBuilder::new())).build(ctx)
423        });
424    }
425}