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#[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 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 pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
103 FerroProperty::register::<Border, _>("Background", None)
104 }
105 );
106
107 ferro_property!(
108 pub fn background_sizing_property() -> StyledProperty<BackgroundSizing> {
110 FerroProperty::register::<Border, _>("BackgroundSizing", BackgroundSizing::CenterBorder)
111 }
112 );
113
114 ferro_property!(
115 pub fn border_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
117 FerroProperty::register::<Border, _>("BorderBrush", None)
118 }
119 );
120
121 ferro_property!(
122 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 pub fn corner_radius_property() -> StyledProperty<CornerRadius> {
135 FerroProperty::register::<Border, _>("CornerRadius", CornerRadius::default())
136 }
137 );
138
139 ferro_property!(
140 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 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 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 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 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 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 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 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 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 pub fn clip_to_bounds_radius(&self) -> CornerRadius {
256 self.corner_radius()
257 }
258}