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