Skip to main content

ferroui_controls/presenters/
content_presenter.rs

1use crate::utils::debug_display::{append_optional_boxed_value, build_base_debug_display};
2use super::as_content_presenter_host;
3use crate::documents::TextElement;
4use crate::metadata::PseudoClassesAttribute;
5use crate::templates::{FuncDataTemplate, IDataTemplate};
6use crate::{Border, ContentControl, Control, ControlImpl, Decorator, TextBlock};
7use ferroui_base::collections::FerroList;
8use ferroui_base::input::InputElementImpl;
9use ferroui_base::interactivity::InteractiveImpl;
10use ferroui_base::layout::{HorizontalAlignment, LayoutHelper, Layoutable, LayoutableImpl, VerticalAlignment};
11use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
12use crate::utils::BorderRenderHelper;
13use ferroui_base::media::{
14    BackgroundSizing, BoxShadows, DrawingContext, FontFamily, FontStretch, FontStyle, FontWeight, IBrush,
15    TextAlignment, TextTrimming, TextWrapping,
16};
17use ferroui_base::utilities::MathUtilities;
18use ferroui_base::{
19    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, CornerRadius, DirectProperty,
20    FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Point, Rect, Ref,
21    Size, StyledElement, StyledElementImpl, StyledElementImplExt, StyledProperty, Thickness, Vector, Visual,
22    VisualImpl, WeakRef,
23};
24use std::cell::{Cell, RefCell};
25use std::rc::Rc;
26
27/// Presents a single item of data inside a templated control.
28#[repr(C)]
29pub struct ContentPresenter {
30    base: Control,
31    child: RefCell<Option<Ref<Control>>>,
32    created_child: Cell<bool>,
33    recycling_data_template: RefCell<Option<Rc<dyn IDataTemplate>>>,
34    /// The data context to use for the next child update instead of the one
35    /// derived from the content, when set.
36    override_data_context: RefCell<Option<Option<BoxedValue>>>,
37    layout_thickness: Cell<Option<Thickness>>,
38    scale: Cell<f64>,
39    host: RefCell<Option<WeakRef<FerroObject>>>,
40    border_renderer: RefCell<BorderRenderHelper>,
41}
42
43ferro_class!(ContentPresenter: Control);
44ferroui_base::ferro_class_info!(ContentPresenter { new: ContentPresenter::new });
45ferro_impl_classes!(ContentPresenter: InteractiveImpl, InputElementImpl, ControlImpl);
46
47impl VisualImpl for ContentPresenter {
48    fn render(this: &Self, context: &mut DrawingContext) {
49        this.border_renderer.borrow_mut().render(
50            context,
51            this.bounds().size(),
52            this.layout_thickness(),
53            this.corner_radius(),
54            this.background_sizing(),
55            this.background().as_ref(),
56            this.border_brush().as_ref(),
57            &this.box_shadow(),
58        );
59    }
60}
61
62ferroui_base::ferro_overrides! { impl FerroObjectImpl for ContentPresenter {
63    fn constructed(this: &Self) {
64        Self::parent_constructed(this);
65        this.update_pseudo_classes();
66    }
67
68    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
69        Self::parent_on_property_changed(this, change);
70        match change.property().name() {
71            "Content" | "ContentTemplate" => this.content_changed(change),
72            "TemplatedParent" => this.templated_parent_changed(change),
73            "UseLayoutRounding" | "BorderThickness" => this.layout_thickness.set(None),
74            _ => {}
75        }
76    }
77} }
78
79impl StyledElementImpl for ContentPresenter {
80    fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
81        Self::parent_on_attached_to_logical_tree(this, e);
82        *this.recycling_data_template.borrow_mut() = None;
83        this.created_child.set(false);
84        this.invalidate_measure();
85    }
86}
87
88impl LayoutableImpl for ContentPresenter {
89    fn apply_template(this: &Self) {
90        if !this.created_child.get() && this.is_attached_to_logical_tree() {
91            this.update_child();
92        }
93    }
94
95    fn measure_override(this: &Self, available_size: Size) -> Size {
96        let child = this.child();
97        LayoutHelper::measure_child_with_border(
98            child.as_deref().map(|c| -> &Layoutable { c }),
99            available_size,
100            this.padding(),
101            this.border_thickness(),
102        )
103    }
104
105    fn arrange_override(this: &Self, final_size: Size) -> Size {
106        this.arrange_override_impl(final_size, Vector::default())
107    }
108}
109
110impl ContentPresenter {
111    /// The pseudoclasses set by the class.
112    pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":empty"]);
113}
114
115ferroui_base::ferro_properties! { impl ContentPresenter {
116    ferro_property!(
117        /// Defines the `Background` property.
118        pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
119            Border::background_property().add_owner::<ContentPresenter>()
120        }
121    );
122
123    ferro_property!(
124        /// Defines the `BackgroundSizing` property.
125        pub fn background_sizing_property() -> StyledProperty<BackgroundSizing> {
126            Border::background_sizing_property().add_owner::<ContentPresenter>()
127        }
128    );
129
130    ferro_property!(
131        /// Defines the `BorderBrush` property.
132        pub fn border_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
133            Border::border_brush_property().add_owner::<ContentPresenter>()
134        }
135    );
136
137    ferro_property!(
138        /// Defines the `BorderThickness` property.
139        pub fn border_thickness_property() -> StyledProperty<Thickness> {
140            Border::border_thickness_property().add_owner::<ContentPresenter>()
141        }
142    );
143
144    ferro_property!(
145        /// Defines the `CornerRadius` property.
146        pub fn corner_radius_property() -> StyledProperty<CornerRadius> {
147            Border::corner_radius_property().add_owner::<ContentPresenter>()
148        }
149    );
150
151    ferro_property!(
152        /// Defines the `BoxShadow` property.
153        pub fn box_shadow_property() -> StyledProperty<BoxShadows> {
154            Border::box_shadow_property().add_owner::<ContentPresenter>()
155        }
156    );
157
158    ferro_property!(
159        /// Defines the `Foreground` property.
160        pub fn foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
161            TextElement::foreground_property().add_owner::<ContentPresenter>()
162        }
163    );
164
165    ferro_property!(
166        /// Defines the `FontFamily` property.
167        pub fn font_family_property() -> StyledProperty<FontFamily> {
168            TextElement::font_family_property().add_owner::<ContentPresenter>()
169        }
170    );
171
172    ferro_property!(
173        /// Defines the `FontSize` property.
174        pub fn font_size_property() -> StyledProperty<f64> {
175            TextElement::font_size_property().add_owner::<ContentPresenter>()
176        }
177    );
178
179    ferro_property!(
180        /// Defines the `FontStyle` property.
181        pub fn font_style_property() -> StyledProperty<FontStyle> {
182            TextElement::font_style_property().add_owner::<ContentPresenter>()
183        }
184    );
185
186    ferro_property!(
187        /// Defines the `FontWeight` property.
188        pub fn font_weight_property() -> StyledProperty<FontWeight> {
189            TextElement::font_weight_property().add_owner::<ContentPresenter>()
190        }
191    );
192
193    ferro_property!(
194        /// Defines the `FontStretch` property.
195        pub fn font_stretch_property() -> StyledProperty<FontStretch> {
196            TextElement::font_stretch_property().add_owner::<ContentPresenter>()
197        }
198    );
199
200    ferro_property!(
201        /// Defines the `TextAlignment` property.
202        pub fn text_alignment_property() -> StyledProperty<TextAlignment> {
203            TextBlock::text_alignment_property().add_owner::<ContentPresenter>()
204        }
205    );
206
207    ferro_property!(
208        /// Defines the `TextWrapping` property.
209        pub fn text_wrapping_property() -> StyledProperty<TextWrapping> {
210            TextBlock::text_wrapping_property().add_owner::<ContentPresenter>()
211        }
212    );
213
214    ferro_property!(
215        /// Defines the `TextTrimming` property.
216        pub fn text_trimming_property() -> StyledProperty<Rc<dyn TextTrimming>> {
217            TextBlock::text_trimming_property().add_owner::<ContentPresenter>()
218        }
219    );
220
221    ferro_property!(
222        /// Defines the `LineHeight` property.
223        pub fn line_height_property() -> StyledProperty<f64> {
224            TextBlock::line_height_property().add_owner::<ContentPresenter>()
225        }
226    );
227
228    ferro_property!(
229        /// Defines the `LetterSpacing` property.
230        pub fn letter_spacing_property() -> StyledProperty<f64> {
231            TextElement::letter_spacing_property().add_owner::<ContentPresenter>()
232        }
233    );
234
235    ferro_property!(
236        /// Defines the `MaxLines` property.
237        pub fn max_lines_property() -> StyledProperty<i32> {
238            TextBlock::max_lines_property().add_owner::<ContentPresenter>()
239        }
240    );
241
242    ferro_property!(
243        /// Defines the `Child` property.
244        pub fn child_property() -> DirectProperty<ContentPresenter, Option<Ref<Control>>> {
245            FerroProperty::register_direct::<ContentPresenter, _>("Child", |o| o.child(), None, None)
246        }
247    );
248
249    ferro_property!(
250        /// Defines the `Content` property.
251        pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
252            ContentControl::content_property().add_owner::<ContentPresenter>()
253        }
254    );
255
256    ferro_property!(
257        /// Defines the `ContentTemplate` property.
258        pub fn content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
259            ContentControl::content_template_property().add_owner::<ContentPresenter>()
260        }
261    );
262
263    ferro_property!(
264        /// Defines the `HorizontalContentAlignment` property.
265        pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
266            ContentControl::horizontal_content_alignment_property().add_owner::<ContentPresenter>()
267        }
268    );
269
270    ferro_property!(
271        /// Defines the `VerticalContentAlignment` property.
272        pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
273            ContentControl::vertical_content_alignment_property().add_owner::<ContentPresenter>()
274        }
275    );
276
277    ferro_property!(
278        /// Defines the `Padding` property.
279        pub fn padding_property() -> StyledProperty<Thickness> {
280            Decorator::padding_property().add_owner::<ContentPresenter>()
281        }
282    );
283
284    ferro_property!(
285        /// Defines the `RecognizesAccessKey` property.
286        pub fn recognizes_access_key_property() -> StyledProperty<bool> {
287            FerroProperty::register::<ContentPresenter, _>("RecognizesAccessKey", false)
288        }
289    );
290} }
291
292impl ContentPresenter {
293    fn static_constructor() {
294        Visual::affects_render::<ContentPresenter>(&[
295            Self::background_property().as_property(),
296            Self::background_sizing_property().as_property(),
297            Self::border_brush_property().as_property(),
298            Self::border_thickness_property().as_property(),
299            Self::box_shadow_property().as_property(),
300            Self::corner_radius_property().as_property(),
301        ]);
302        Layoutable::affects_arrange::<ContentPresenter>(&[
303            Self::horizontal_content_alignment_property().as_property(),
304            Self::vertical_content_alignment_property().as_property(),
305        ]);
306        Layoutable::affects_measure::<ContentPresenter>(&[
307            Self::border_thickness_property().as_property(),
308            Self::padding_property().as_property(),
309        ]);
310    }
311
312    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
313    pub fn construct() -> Self {
314        Self {
315            base: Control::construct(),
316            child: RefCell::new(None),
317            created_child: Cell::new(false),
318            recycling_data_template: RefCell::new(None),
319            override_data_context: RefCell::new(None),
320            layout_thickness: Cell::new(None),
321            scale: Cell::new(0.0),
322            host: RefCell::new(None),
323            border_renderer: RefCell::new(BorderRenderHelper::new()),
324        }
325    }
326
327    pub fn new() -> Ref<Self> {
328        instantiate(Self::construct())
329    }
330
331    /// A brush with which to paint the background.
332    pub fn background(&self) -> Option<Rc<dyn IBrush>> {
333        self.get_value(Self::background_property())
334    }
335
336    pub fn set_background(&self, value: Option<Rc<dyn IBrush>>) {
337        self.set_value(Self::background_property(), value)
338    }
339
340    /// How the background is drawn relative to the border.
341    pub fn background_sizing(&self) -> BackgroundSizing {
342        self.get_value(Self::background_sizing_property())
343    }
344
345    pub fn set_background_sizing(&self, value: BackgroundSizing) {
346        self.set_value(Self::background_sizing_property(), value)
347    }
348
349    /// A brush with which to paint the border.
350    pub fn border_brush(&self) -> Option<Rc<dyn IBrush>> {
351        self.get_value(Self::border_brush_property())
352    }
353
354    pub fn set_border_brush(&self, value: Option<Rc<dyn IBrush>>) {
355        self.set_value(Self::border_brush_property(), value)
356    }
357
358    /// The thickness of the border.
359    pub fn border_thickness(&self) -> Thickness {
360        self.get_value(Self::border_thickness_property())
361    }
362
363    pub fn set_border_thickness(&self, value: Thickness) {
364        self.set_value(Self::border_thickness_property(), value)
365    }
366
367    /// The radius of the border rounded corners.
368    pub fn corner_radius(&self) -> CornerRadius {
369        self.get_value(Self::corner_radius_property())
370    }
371
372    pub fn set_corner_radius(&self, value: CornerRadius) {
373        self.set_value(Self::corner_radius_property(), value)
374    }
375
376    /// The box shadow effect parameters.
377    pub fn box_shadow(&self) -> BoxShadows {
378        self.get_value(Self::box_shadow_property())
379    }
380
381    pub fn set_box_shadow(&self, value: BoxShadows) {
382        self.set_value(Self::box_shadow_property(), value)
383    }
384
385    /// A brush used to paint the text.
386    pub fn foreground(&self) -> Option<Rc<dyn IBrush>> {
387        self.get_value(Self::foreground_property())
388    }
389
390    pub fn set_foreground(&self, value: Option<Rc<dyn IBrush>>) {
391        self.set_value(Self::foreground_property(), value)
392    }
393
394    /// The font family.
395    pub fn font_family(&self) -> FontFamily {
396        self.get_value(Self::font_family_property())
397    }
398
399    pub fn set_font_family(&self, value: FontFamily) {
400        self.set_value(Self::font_family_property(), value)
401    }
402
403    /// The font size.
404    pub fn font_size(&self) -> f64 {
405        self.get_value(Self::font_size_property())
406    }
407
408    pub fn set_font_size(&self, value: f64) {
409        self.set_value(Self::font_size_property(), value)
410    }
411
412    /// The font style.
413    pub fn font_style(&self) -> FontStyle {
414        self.get_value(Self::font_style_property())
415    }
416
417    pub fn set_font_style(&self, value: FontStyle) {
418        self.set_value(Self::font_style_property(), value)
419    }
420
421    /// The font weight.
422    pub fn font_weight(&self) -> FontWeight {
423        self.get_value(Self::font_weight_property())
424    }
425
426    pub fn set_font_weight(&self, value: FontWeight) {
427        self.set_value(Self::font_weight_property(), value)
428    }
429
430    /// The font stretch.
431    pub fn font_stretch(&self) -> FontStretch {
432        self.get_value(Self::font_stretch_property())
433    }
434
435    pub fn set_font_stretch(&self, value: FontStretch) {
436        self.set_value(Self::font_stretch_property(), value)
437    }
438
439    /// The text alignment.
440    pub fn text_alignment(&self) -> TextAlignment {
441        self.get_value(Self::text_alignment_property())
442    }
443
444    pub fn set_text_alignment(&self, value: TextAlignment) {
445        self.set_value(Self::text_alignment_property(), value)
446    }
447
448    /// The text wrapping.
449    pub fn text_wrapping(&self) -> TextWrapping {
450        self.get_value(Self::text_wrapping_property())
451    }
452
453    pub fn set_text_wrapping(&self, value: TextWrapping) {
454        self.set_value(Self::text_wrapping_property(), value)
455    }
456
457    /// The text trimming.
458    pub fn text_trimming(&self) -> Rc<dyn TextTrimming> {
459        self.get_value(Self::text_trimming_property())
460    }
461
462    pub fn set_text_trimming(&self, value: Rc<dyn TextTrimming>) {
463        self.set_value(Self::text_trimming_property(), value)
464    }
465
466    /// The line height.
467    pub fn line_height(&self) -> f64 {
468        self.get_value(Self::line_height_property())
469    }
470
471    pub fn set_line_height(&self, value: f64) {
472        self.set_value(Self::line_height_property(), value)
473    }
474
475    /// The letter spacing.
476    pub fn letter_spacing(&self) -> f64 {
477        self.get_value(Self::letter_spacing_property())
478    }
479
480    pub fn set_letter_spacing(&self, value: f64) {
481        self.set_value(Self::letter_spacing_property(), value)
482    }
483
484    /// The max lines.
485    pub fn max_lines(&self) -> i32 {
486        self.get_value(Self::max_lines_property())
487    }
488
489    pub fn set_max_lines(&self, value: i32) {
490        self.set_value(Self::max_lines_property(), value)
491    }
492
493    /// The control displayed by the presenter.
494    pub fn child(&self) -> Option<Ref<Control>> {
495        self.child.borrow().clone()
496    }
497
498    fn set_child(&self, value: Option<Ref<Control>>) {
499        self.set_and_raise(Self::child_property(), &self.child, value);
500    }
501
502    /// The content to be displayed by the presenter.
503    pub fn content(&self) -> Option<BoxedValue> {
504        self.get_value(Self::content_property())
505    }
506
507    pub fn set_content(&self, value: Option<BoxedValue>) {
508        self.set_value(Self::content_property(), value)
509    }
510
511    /// The data template used to display the content of the control.
512    pub fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
513        self.get_value(Self::content_template_property())
514    }
515
516    pub fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
517        self.set_value(Self::content_template_property(), value)
518    }
519
520    /// The horizontal alignment of the content within the border the
521    /// control.
522    pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
523        self.get_value(Self::horizontal_content_alignment_property())
524    }
525
526    pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
527        self.set_value(Self::horizontal_content_alignment_property(), value)
528    }
529
530    /// The vertical alignment of the content within the border of the
531    /// control.
532    pub fn vertical_content_alignment(&self) -> VerticalAlignment {
533        self.get_value(Self::vertical_content_alignment_property())
534    }
535
536    pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
537        self.set_value(Self::vertical_content_alignment_property(), value)
538    }
539
540    /// The space between the border and the child control.
541    pub fn padding(&self) -> Thickness {
542        self.get_value(Self::padding_property())
543    }
544
545    pub fn set_padding(&self, value: Thickness) {
546        self.set_value(Self::padding_property(), value)
547    }
548
549    /// Determine if the presenter should use access text in its style.
550    pub fn recognizes_access_key(&self) -> bool {
551        self.get_value(Self::recognizes_access_key_property())
552    }
553
554    pub fn set_recognizes_access_key(&self, value: bool) {
555        self.set_value(Self::recognizes_access_key_property(), value)
556    }
557
558    /// The host content control.
559    pub fn host(&self) -> Option<Ref<FerroObject>> {
560        self.host.borrow().as_ref().and_then(WeakRef::upgrade)
561    }
562
563    /// Sets the content and the data context of the presenter at once: the
564    /// child is created with `data_context` instead of the data context
565    /// derived from the content.
566    pub fn set_content_with_data_context(&self, content: Option<BoxedValue>, data_context: Option<BoxedValue>) {
567        *self.override_data_context.borrow_mut() = Some(data_context.clone());
568
569        // If Content didn't change, `update_child` wasn't called and the
570        // override wasn't consumed. Apply the data context directly. The
571        // guard also runs when setting the content unwinds.
572        struct Finally<'a>(&'a ContentPresenter, Option<BoxedValue>);
573
574        impl Drop for Finally<'_> {
575            fn drop(&mut self) {
576                let is_set = self.0.override_data_context.borrow().is_some();
577                if is_set {
578                    *self.0.override_data_context.borrow_mut() = None;
579                    self.0.set_data_context(self.1.take());
580                }
581            }
582        }
583
584        let _finally = Finally(self, data_context);
585        self.set_current_value(Self::content_property(), content);
586    }
587
588    /// Updates the `child` control based on the `content`.
589    ///
590    /// Usually the child control is created automatically when
591    /// `apply_template` is called; however for this to happen, the control
592    /// needs to be attached to a logical tree (if the control is not attached
593    /// to the logical tree, it is reasonable to expect that the data
594    /// templates needed for the child are not yet available). This method
595    /// forces the child to be updated regardless.
596    pub fn update_child(&self) {
597        let content = self.content();
598        self.update_child_with(content);
599    }
600
601    fn update_child_with(&self, content: Option<BoxedValue>) {
602        let content_template = self.content_template();
603        let old_child = self.child();
604        let new_child = self.create_child(content.as_ref(), old_child.as_ref(), content_template.as_ref());
605
606        // Remove the old child if we're not recycling it.
607        if new_child != old_child {
608            ferroui_base::perf_count!(ContentPresenterChildrenReplaced);
609            if let Some(old_child) = &old_child {
610                self.visual_children().remove(&old_child.clone().upcast());
611                self.with_effective_logical_children(|logical_children| {
612                    logical_children.remove(&old_child.clone().upcast());
613                });
614                old_child.set_inheritance_parent(old_child.parent().map(Ref::upcast::<FerroObject>));
615            }
616        }
617
618        // Consume the override immediately so any reentrant/cascading calls
619        // to `update_child` don't incorrectly apply the stale override.
620        let override_data_context = self.override_data_context.borrow_mut().take();
621
622        // Set the data context: use the caller-provided override if set,
623        // otherwise set to content when a template is present or content
624        // isn't a control, or clear for template-less control content.
625        let content_is_control = content.as_ref().is_some_and(|content| Control::from_boxed(content).is_some());
626        if let Some(data_context) = override_data_context {
627            self.set_data_context(data_context);
628        } else if content_template.is_some() || !content_is_control {
629            self.set_data_context(content);
630        } else {
631            self.clear_value(StyledElement::data_context_property());
632        }
633
634        // Update the child.
635        match new_child {
636            None => self.set_child(None),
637            Some(new_child) => {
638                if Some(&new_child) != old_child.as_ref() {
639                    new_child.set_inheritance_parent(self.to_ref().upcast::<FerroObject>());
640                    self.set_child(Some(new_child.clone()));
641
642                    self.with_effective_logical_children(|logical_children| {
643                        let logical: Ref<StyledElement> = new_child.clone().upcast();
644                        if !logical_children.contains(&logical) {
645                            logical_children.add(logical);
646                        }
647                    });
648
649                    self.visual_children().add(new_child.upcast());
650                }
651            }
652        }
653
654        self.created_child.set(true);
655    }
656
657    /// Runs `f` with the logical children of the host, or with the
658    /// presenter's own when it has no host.
659    fn with_effective_logical_children(&self, f: impl FnOnce(&FerroList<Ref<StyledElement>>)) {
660        let host = self.host();
661        match host.as_ref().and_then(|host| as_content_presenter_host(host)) {
662            Some(host) => f(host.logical_children()),
663            None => f(self.logical_children()),
664        }
665    }
666
667    /// The border thickness used for rendering: rounded to device pixels
668    /// when layout rounding is in use.
669    pub fn layout_thickness(&self) -> Thickness {
670        self.verify_scale();
671
672        match self.layout_thickness.get() {
673            Some(thickness) => thickness,
674            None => {
675                let mut border_thickness = self.border_thickness();
676
677                if self.use_layout_rounding() {
678                    border_thickness = LayoutHelper::round_layout_thickness(border_thickness, self.scale.get());
679                }
680
681                self.layout_thickness.set(Some(border_thickness));
682                border_thickness
683            }
684        }
685    }
686
687    fn verify_scale(&self) {
688        let current_scale = LayoutHelper::get_layout_scale(self);
689        if MathUtilities::are_close(current_scale, self.scale.get()) {
690            return;
691        }
692
693        self.scale.set(current_scale);
694        self.layout_thickness.set(None);
695    }
696
697    fn create_child(
698        &self,
699        content: Option<&BoxedValue>,
700        old_child: Option<&Ref<Control>>,
701        template: Option<&Rc<dyn IDataTemplate>>,
702    ) -> Option<Ref<Control>> {
703        let mut new_child = content.and_then(Control::from_boxed);
704
705        // We want to allow creating the child from the template, if the
706        // content is null. But it's important to not use data templates,
707        // otherwise every content presenter without content set would get a
708        // child.
709        if (new_child.is_none() && (content.is_some() || template.is_some()))
710            || (new_child.is_some() && template.is_some())
711        {
712            let data_template: Rc<dyn IDataTemplate> = match self.find_data_template(content, template) {
713                Some(data_template) => data_template,
714                None if self.recognizes_access_key() => FuncDataTemplate::access(),
715                None => FuncDataTemplate::default_template(),
716            };
717
718            if let Some(rdt) = data_template.as_recycling_data_template() {
719                let is_same = self.recycling_data_template.borrow().as_ref().is_some_and(|current| {
720                    std::ptr::addr_eq(Rc::as_ptr(current), Rc::as_ptr(&data_template))
721                });
722                let to_recycle = if is_same { old_child.cloned() } else { None };
723                new_child = rdt.build_with_existing(content, to_recycle);
724                *self.recycling_data_template.borrow_mut() = Some(data_template.clone());
725            } else {
726                new_child = data_template.build(&content.cloned());
727                *self.recycling_data_template.borrow_mut() = None;
728            }
729        } else {
730            *self.recycling_data_template.borrow_mut() = None;
731        }
732
733        new_child
734    }
735
736    /// Arranges the child within `final_size`, with the origin of the
737    /// content area displaced by `offset`.
738    pub fn arrange_override_impl(&self, final_size: Size, offset: Vector) -> Size {
739        let Some(child) = self.child() else { return final_size };
740
741        let use_layout_rounding = self.use_layout_rounding();
742        let scale = LayoutHelper::get_layout_scale(self);
743        let mut padding = self.padding();
744        let mut border_thickness = self.border_thickness();
745
746        if use_layout_rounding {
747            padding = LayoutHelper::round_layout_thickness(padding, scale);
748            border_thickness = LayoutHelper::round_layout_thickness(border_thickness, scale);
749        }
750
751        padding = padding + border_thickness;
752        let horizontal_content_alignment = self.horizontal_content_alignment();
753        let vertical_content_alignment = self.vertical_content_alignment();
754        let mut available_size = final_size;
755        let mut size_for_child = available_size;
756        let mut origin_x = offset.x;
757        let mut origin_y = offset.y;
758        let desired_size = self.desired_size();
759
760        if horizontal_content_alignment != HorizontalAlignment::Stretch {
761            size_for_child = size_for_child.with_width(size_for_child.width.min(desired_size.width));
762        }
763
764        if vertical_content_alignment != VerticalAlignment::Stretch {
765            size_for_child = size_for_child.with_height(size_for_child.height.min(desired_size.height));
766        }
767
768        if use_layout_rounding {
769            size_for_child = LayoutHelper::round_layout_size_up(size_for_child, scale);
770            available_size = LayoutHelper::round_layout_size_up(available_size, scale);
771        }
772
773        match horizontal_content_alignment {
774            HorizontalAlignment::Center => origin_x += (available_size.width - size_for_child.width) / 2.0,
775            HorizontalAlignment::Right => origin_x += available_size.width - size_for_child.width,
776            _ => {}
777        }
778
779        match vertical_content_alignment {
780            VerticalAlignment::Center => origin_y += (available_size.height - size_for_child.height) / 2.0,
781            VerticalAlignment::Bottom => origin_y += available_size.height - size_for_child.height,
782            _ => {}
783        }
784
785        let mut origin = Point::new(origin_x, origin_y);
786
787        if use_layout_rounding {
788            origin = LayoutHelper::round_layout_point(origin, scale);
789        }
790
791        let bounds_for_child = Rect::from_position_size(origin, size_for_child).deflate_thickness(padding);
792
793        child.arrange(bounds_for_child);
794
795        final_size
796    }
797
798    /// Called when the `Content` or `ContentTemplate` property changes.
799    fn content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
800        self.created_child.set(false);
801
802        if self.is_attached_to_logical_tree() {
803            if e.property().name() == "Content" {
804                self.update_child_with(e.get_new_value::<Option<BoxedValue>>());
805            } else {
806                self.update_child();
807            }
808        } else if let Some(child) = self.child() {
809            self.visual_children().remove(&child.clone().upcast());
810            self.with_effective_logical_children(|logical_children| {
811                logical_children.remove(&child.clone().upcast());
812            });
813            child.set_inheritance_parent(child.parent().map(Ref::upcast::<FerroObject>));
814            self.set_child(None);
815            *self.recycling_data_template.borrow_mut() = None;
816        }
817
818        self.update_pseudo_classes();
819        self.invalidate_measure();
820    }
821
822    fn update_pseudo_classes(&self) {
823        self.pseudo_classes().set(":empty", self.content().is_none());
824    }
825
826    fn templated_parent_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
827        let new_value = e.get_new_value::<Option<Ref<FerroObject>>>();
828        let registered = new_value.as_ref().is_some_and(|parent| {
829            as_content_presenter_host(parent).is_some_and(|host| host.register_content_presenter(self))
830        });
831        *self.host.borrow_mut() = if registered { new_value.as_ref().map(Ref::downgrade) } else { None };
832    }
833}
834
835impl ContentPresenter {
836    /// Appends the text that describes the presenter in diagnostics: the base description,
837    /// its host and, with the content, its content.
838    pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
839        build_base_debug_display(self, builder);
840
841        let host: Option<BoxedValue> = self.host().map(|host| Rc::new(host) as BoxedValue);
842        append_optional_boxed_value(builder, "Host", host.as_ref(), false);
843
844        if include_content {
845            append_optional_boxed_value(builder, "Content", self.content().as_ref(), true);
846        }
847    }
848}