Skip to main content

ferroui_controls/
decorator.rs

1use crate::{Control, ControlImpl};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl};
5use ferroui_base::{
6    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl,
7    FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref, Size, StyledElementImpl, StyledProperty,
8    StyledPropertyOptions, Thickness, VisualImpl,
9};
10
11/// Base class for controls which decorate a single child control.
12#[repr(C)]
13pub struct Decorator {
14    base: Control,
15}
16
17ferro_class!(Decorator: Control);
18ferroui_base::ferro_class_info!(Decorator { new: Decorator::new });
19ferro_impl_classes!(Decorator: StyledElementImpl, VisualImpl, InteractiveImpl, InputElementImpl, ControlImpl);
20
21ferroui_base::ferro_impl_classes!(Decorator: FerroObjectImpl);
22
23impl LayoutableImpl for Decorator {
24    fn measure_override(this: &Self, available_size: Size) -> Size {
25        let child = this.child();
26        LayoutHelper::measure_child(child.as_deref().map(|c| -> &Layoutable { c }), available_size, this.padding())
27    }
28
29    fn arrange_override(this: &Self, final_size: Size) -> Size {
30        let child = this.child();
31        LayoutHelper::arrange_child(child.as_deref().map(|c| -> &Layoutable { c }), final_size, this.padding())
32    }
33}
34
35ferroui_base::ferro_properties! { impl Decorator {
36    ferro_property!(
37        /// Defines the `Child` property.
38        pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
39            FerroProperty::register::<Decorator, _>("Child", None)
40        }
41    );
42
43    ferro_property!(
44        /// Defines the `Padding` property.
45        pub fn padding_property() -> StyledProperty<Thickness> {
46            FerroProperty::register_with::<Decorator, _>(
47                "Padding",
48                StyledPropertyOptions::new(Thickness::default())
49                    .validate(|value| Layoutable::margin_property().validate_value().is_none_or(|validate| validate(value))),
50            )
51        }
52    );
53} }
54
55impl Decorator {
56    fn static_constructor() {
57        Layoutable::affects_measure::<Decorator>(&[
58            Self::child_property().as_property(),
59            Self::padding_property().as_property(),
60        ]);
61        Self::child_property().changed().add_class_handler::<Decorator>(|x, e| x.child_changed(e));
62    }
63
64    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
65    pub fn construct() -> Self {
66        Self { base: Control::construct() }
67    }
68
69    pub fn new() -> Ref<Self> {
70        instantiate(Self::construct())
71    }
72
73    /// The decorated control.
74    pub fn child(&self) -> Option<Ref<Control>> {
75        self.get_value(Self::child_property())
76    }
77
78    pub fn set_child(&self, value: impl Into<Nullable<Control>>) {
79        self.set_value(Self::child_property(), value.into().0)
80    }
81
82    /// The padding to place around the child control.
83    pub fn padding(&self) -> Thickness {
84        self.get_value(Self::padding_property())
85    }
86
87    pub fn set_padding(&self, value: Thickness) {
88        self.set_value(Self::padding_property(), value)
89    }
90
91    /// Called when the `Child` property changes.
92    fn child_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
93        let (old_child, new_child) = e.get_old_and_new_value::<Option<Ref<Control>>>();
94
95        if let Some(old_child) = old_child {
96            old_child.set_parent(None);
97            self.logical_children().clear();
98            self.visual_children().remove(&old_child.upcast());
99        }
100
101        if let Some(new_child) = new_child {
102            new_child.set_parent(self.to_ref());
103            self.visual_children().add(new_child.clone().upcast());
104            self.logical_children().add(new_child.upcast());
105        }
106    }
107}