Skip to main content

ferroui_controls/
border.rs

1use crate::{ControlImpl, Decorator};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl};
5use crate::utils::BorderRenderHelper;
6use ferroui_base::media::{BackgroundSizing, BoxShadows, DrawingContext, IBrush};
7use ferroui_base::utilities::MathUtilities;
8use ferroui_base::{
9    ferro_class, ferro_impl_classes, ferro_property, instantiate, CornerRadius, FerroObjectImpl, FerroObjectImplExt,
10    FerroProperty, FerroPropertyChangedEventArgs, Ref, Size, StyledElementImpl, StyledProperty,
11    StyledPropertyOptions, Thickness, Visual, VisualImpl,
12};
13use std::cell::{Cell, RefCell};
14use std::rc::Rc;
15
16/// A control which decorates a child with a border and background.
17#[repr(C)]
18pub struct Border {
19    base: Decorator,
20    layout_thickness: Cell<Option<Thickness>>,
21    scale: Cell<f64>,
22    border_render_helper: RefCell<BorderRenderHelper>,
23    border_visual: RefCell<Option<crate::border_visual::CompositionBorderVisual>>,
24}
25
26ferro_class!(Border: Decorator);
27ferroui_base::ferro_class_info!(Border { new: Border::new });
28ferro_impl_classes!(Border: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
29
30impl VisualImpl for Border {
31    fn render(this: &Self, context: &mut DrawingContext) {
32        this.border_render_helper.borrow_mut().render(
33            context,
34            this.bounds().size(),
35            this.layout_thickness(),
36            this.corner_radius(),
37            this.background_sizing(),
38            this.background().as_ref(),
39            this.border_brush().as_ref(),
40            &this.box_shadow(),
41        );
42    }
43
44    // The rounded clip of the immediate renderer (`IVisualWithRoundRectClip`
45    // upstream).
46    fn clip_to_bounds_radius(this: &Self) -> Option<CornerRadius> {
47        Some(this.corner_radius())
48    }
49
50    fn create_composition_visual(
51        this: &Self,
52        compositor: &Rc<ferroui_base::rendering::composition::Compositor>,
53    ) -> ferroui_base::rendering::composition::CompositionDrawListVisual {
54        let border_visual = crate::border_visual::CompositionBorderVisual::new(compositor, this);
55        border_visual.set_corner_radius(this.corner_radius());
56        let result = border_visual.as_draw_list_visual().clone();
57        *this.border_visual.borrow_mut() = Some(border_visual);
58        result
59    }
60}
61
62ferroui_base::ferro_overrides! { impl FerroObjectImpl for Border {
63    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
64        Self::parent_on_property_changed(this, change);
65        match change.property().name() {
66            "UseLayoutRounding" | "BorderThickness" => this.layout_thickness.set(None),
67            "CornerRadius" => {
68                if let Some(border_visual) = &*this.border_visual.borrow() {
69                    border_visual.set_corner_radius(this.corner_radius());
70                }
71            }
72            _ => {}
73        }
74    }
75} }
76
77impl LayoutableImpl for Border {
78    fn measure_override(this: &Self, available_size: Size) -> Size {
79        let child = this.child();
80        LayoutHelper::measure_child_with_border(
81            child.as_deref().map(|c| -> &Layoutable { c }),
82            available_size,
83            this.padding(),
84            this.border_thickness(),
85        )
86    }
87
88    fn arrange_override(this: &Self, final_size: Size) -> Size {
89        let child = this.child();
90        LayoutHelper::arrange_child_with_border(
91            child.as_deref().map(|c| -> &Layoutable { c }),
92            final_size,
93            this.padding(),
94            this.border_thickness(),
95        )
96    }
97}
98
99ferroui_base::ferro_properties! { impl Border {
100    ferro_property!(
101        /// Defines the `Background` property.
102        pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
103            FerroProperty::register::<Border, _>("Background", None)
104        }
105    );
106
107    ferro_property!(
108        /// Defines the `BackgroundSizing` property.
109        pub fn background_sizing_property() -> StyledProperty<BackgroundSizing> {
110            FerroProperty::register::<Border, _>("BackgroundSizing", BackgroundSizing::CenterBorder)
111        }
112    );
113
114    ferro_property!(
115        /// Defines the `BorderBrush` property.
116        pub fn border_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
117            FerroProperty::register::<Border, _>("BorderBrush", None)
118        }
119    );
120
121    ferro_property!(
122        /// Defines the `BorderThickness` property.
123        pub fn border_thickness_property() -> StyledProperty<Thickness> {
124            FerroProperty::register_with::<Border, _>(
125                "BorderThickness",
126                StyledPropertyOptions::new(Thickness::default())
127                    .validate(|value| Layoutable::margin_property().validate_value().is_none_or(|validate| validate(value))),
128            )
129        }
130    );
131
132    ferro_property!(
133        /// Defines the `CornerRadius` property.
134        pub fn corner_radius_property() -> StyledProperty<CornerRadius> {
135            FerroProperty::register::<Border, _>("CornerRadius", CornerRadius::default())
136        }
137    );
138
139    ferro_property!(
140        /// Defines the `BoxShadow` property.
141        pub fn box_shadow_property() -> StyledProperty<BoxShadows> {
142            FerroProperty::register::<Border, _>("BoxShadow", BoxShadows::default())
143        }
144    );
145} }
146
147impl Border {
148    fn static_constructor() {
149        Visual::affects_render::<Border>(&[
150            Self::background_property().as_property(),
151            Self::background_sizing_property().as_property(),
152            Self::border_brush_property().as_property(),
153            Self::border_thickness_property().as_property(),
154            Self::corner_radius_property().as_property(),
155            Self::box_shadow_property().as_property(),
156        ]);
157        Layoutable::affects_measure::<Border>(&[Self::border_thickness_property().as_property()]);
158    }
159
160    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
161    pub fn construct() -> Self {
162        Self { base: Decorator::construct(), layout_thickness: Cell::new(None), scale: Cell::new(0.0), border_render_helper: RefCell::new(BorderRenderHelper::new()), border_visual: RefCell::new(None) }
163    }
164
165    pub fn new() -> Ref<Self> {
166        instantiate(Self::construct())
167    }
168
169    /// A brush with which to paint the background.
170    pub fn background(&self) -> Option<Rc<dyn IBrush>> {
171        self.get_value(Self::background_property())
172    }
173
174    pub fn set_background(&self, value: Option<Rc<dyn IBrush>>) {
175        self.set_value(Self::background_property(), value)
176    }
177
178    /// How the background is drawn relative to the border.
179    pub fn background_sizing(&self) -> BackgroundSizing {
180        self.get_value(Self::background_sizing_property())
181    }
182
183    pub fn set_background_sizing(&self, value: BackgroundSizing) {
184        self.set_value(Self::background_sizing_property(), value)
185    }
186
187    /// A brush with which to paint the border.
188    pub fn border_brush(&self) -> Option<Rc<dyn IBrush>> {
189        self.get_value(Self::border_brush_property())
190    }
191
192    pub fn set_border_brush(&self, value: Option<Rc<dyn IBrush>>) {
193        self.set_value(Self::border_brush_property(), value)
194    }
195
196    /// The thickness of the border.
197    pub fn border_thickness(&self) -> Thickness {
198        self.get_value(Self::border_thickness_property())
199    }
200
201    pub fn set_border_thickness(&self, value: Thickness) {
202        self.set_value(Self::border_thickness_property(), value)
203    }
204
205    /// The radius of the border rounded corners.
206    pub fn corner_radius(&self) -> CornerRadius {
207        self.get_value(Self::corner_radius_property())
208    }
209
210    pub fn set_corner_radius(&self, value: CornerRadius) {
211        self.set_value(Self::corner_radius_property(), value)
212    }
213
214    /// The box shadow effect parameters.
215    pub fn box_shadow(&self) -> BoxShadows {
216        self.get_value(Self::box_shadow_property())
217    }
218
219    pub fn set_box_shadow(&self, value: BoxShadows) {
220        self.set_value(Self::box_shadow_property(), value)
221    }
222
223    /// The border thickness used for rendering: rounded to device pixels
224    /// when layout rounding is in use.
225    pub fn layout_thickness(&self) -> Thickness {
226        self.verify_scale();
227
228        match self.layout_thickness.get() {
229            Some(thickness) => thickness,
230            None => {
231                let mut border_thickness = self.border_thickness();
232
233                if self.use_layout_rounding() {
234                    border_thickness = LayoutHelper::round_layout_thickness(border_thickness, self.scale.get());
235                }
236
237                self.layout_thickness.set(Some(border_thickness));
238                border_thickness
239            }
240        }
241    }
242
243    fn verify_scale(&self) {
244        let current_scale = LayoutHelper::get_layout_scale(self);
245        if MathUtilities::are_close(current_scale, self.scale.get()) {
246            return;
247        }
248
249        self.scale.set(current_scale);
250        self.layout_thickness.set(None);
251    }
252
253    /// The corner radius with which the visual is clipped when it clips to
254    /// its bounds.
255    pub fn clip_to_bounds_radius(&self) -> CornerRadius {
256        self.corner_radius()
257    }
258}