Skip to main content

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