Skip to main content

ferroui_controls/primitives/
templated_control.rs

1use super::TemplateAppliedEventArgs;
2use crate::documents::TextElement;
3use crate::templates::IControlTemplate;
4use crate::{Border, Control, ControlImpl, Decorator};
5use ferroui_base::controls::ResourcesChangedEventArgs;
6use ferroui_base::input::InputElementImpl;
7use ferroui_base::interactivity::{Interactive, InteractiveImpl, RoutedEvent, RoutedEventHandlerToken, RoutingStrategies};
8use ferroui_base::layout::LayoutableImpl;
9use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
10use ferroui_base::media::{
11    BackgroundSizing, FontFamily, FontFeatureCollection, FontStretch, FontStyle, FontWeight, IBrush,
12};
13use ferroui_base::{
14    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AttachedProperty,
15    CornerRadius, FerroObject, FerroObjectImpl, FerroProperty, FerroPropertyChangedEventArgs,
16    Nullable, Ref, StyledElement, StyledElementImpl, StyledElementImplExt, StyledProperty, Thickness, Visual,
17    VisualImpl,
18};
19use std::cell::RefCell;
20use std::rc::Rc;
21
22/// A lookless control whose visual appearance is defined by its `Template`.
23#[repr(C)]
24pub struct TemplatedControl {
25    base: Control,
26    applied_template: RefCell<Option<Rc<dyn IControlTemplate>>>,
27}
28
29ferro_class! {
30    TemplatedControl: Control, virtuals TemplatedControlImpl: ControlImpl {
31        /// Called when the control's template is applied.
32        ///
33        /// In simple terms, this means the method is called just before the
34        /// control is displayed.
35        fn on_apply_template(this, e: &TemplateAppliedEventArgs);
36        /// Called when the `Template` property changes.
37        fn on_template_changed(this, e: &FerroPropertyChangedEventArgs<'_>);
38    }
39}
40ferroui_base::ferro_class_info!(TemplatedControl { new: TemplatedControl::new });
41
42ferro_impl_classes!(TemplatedControl: VisualImpl, InteractiveImpl, InputElementImpl);
43
44ferroui_base::ferro_impl_classes!(TemplatedControl: FerroObjectImpl);
45
46impl StyledElementImpl for TemplatedControl {
47    fn notify_child_resources_changed(this: &Self, e: ResourcesChangedEventArgs) {
48        if let Some(children) = this.visual_children_snapshot() {
49            for child in children.iter() {
50                child.notify_resources_changed(e, true);
51            }
52        }
53
54        Self::parent_notify_child_resources_changed(this, e);
55    }
56
57    fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
58        if let Some(child) = this.visual_children_snapshot().and_then(|children| children.first().cloned()) {
59            child.notify_attached_to_logical_tree(e);
60        }
61
62        Self::parent_on_attached_to_logical_tree(this, e);
63    }
64
65    fn on_detached_from_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
66        if let Some(child) = this.visual_children_snapshot().and_then(|children| children.first().cloned()) {
67            child.notify_detached_from_logical_tree(e);
68        }
69
70        Self::parent_on_detached_from_logical_tree(this, e);
71    }
72
73    fn on_control_theme_changed(this: &Self) {
74        Self::parent_on_control_theme_changed(this);
75
76        if let Some(children) = this.visual_children_snapshot() {
77            let this_ref: Ref<FerroObject> = this.to_ref().upcast();
78            for child in children.iter() {
79                if child.templated_parent().as_ref() == Some(&this_ref) {
80                    child.on_templated_parent_control_theme_changed();
81                }
82            }
83        }
84    }
85}
86
87impl LayoutableImpl for TemplatedControl {
88    fn apply_template(this: &Self) {
89        let template = this.template();
90
91        // Apply the template if it is not the same as the template already
92        // applied - except for in the case that the template is null and
93        // we're not attached to the logical tree. In that case, the template
94        // has probably been cleared because the style setting the template
95        // has been detached, so we want to wait until it's re-attached to the
96        // logical tree as if it's re-attached to the same tree the template
97        // will be the same and we don't need to do anything.
98        let applied_template = this.applied_template.borrow().clone();
99        if applied_template != template && (template.is_some() || this.is_attached_to_logical_tree()) {
100            if this.visual_children_count() > 0 {
101                for child in this.get_template_descendants() {
102                    child.set_templated_parent(None);
103                    child.set_parent(None);
104                }
105
106                this.visual_children().clear();
107            }
108
109            if let Some(template) = &template {
110                let this_ref = this.to_ref();
111                if let Some(template_result) = template.build(&this_ref) {
112                    let (child, name_scope) = template_result.deconstruct();
113                    Self::apply_templated_parent(&child, Some(&this_ref.clone().upcast()));
114                    child.set_parent(this_ref);
115                    this.visual_children().add(child.upcast());
116
117                    let e = TemplateAppliedEventArgs::new(name_scope);
118                    this.on_apply_template(&e);
119                    this.raise_event(&e);
120                }
121            }
122
123            *this.applied_template.borrow_mut() = template;
124        }
125    }
126}
127
128impl ControlImpl for TemplatedControl {
129    fn get_template_focus_target(this: &Self) -> Option<Ref<Control>> {
130        for child in this.get_template_descendants() {
131            if let Some(control) = child.cast::<Control>() {
132                if Self::get_is_template_focus_target(&control) {
133                    return Some(control);
134                }
135            }
136        }
137
138        Some(this.to_ref().upcast())
139    }
140}
141
142impl TemplatedControlImpl for TemplatedControl {
143    fn on_apply_template(_this: &Self, _e: &TemplateAppliedEventArgs) {}
144
145    fn on_template_changed(this: &Self, _e: &FerroPropertyChangedEventArgs<'_>) {
146        this.invalidate_measure();
147    }
148}
149
150ferroui_base::ferro_properties! { impl TemplatedControl {
151    ferro_property!(
152        /// Defines the `Background` property.
153        pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
154            Border::background_property().add_owner::<TemplatedControl>()
155        }
156    );
157
158    ferro_property!(
159        /// Defines the `BackgroundSizing` property.
160        pub fn background_sizing_property() -> StyledProperty<BackgroundSizing> {
161            Border::background_sizing_property().add_owner::<TemplatedControl>()
162        }
163    );
164
165    ferro_property!(
166        /// Defines the `BorderBrush` property.
167        pub fn border_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
168            Border::border_brush_property().add_owner::<TemplatedControl>()
169        }
170    );
171
172    ferro_property!(
173        /// Defines the `BorderThickness` property.
174        pub fn border_thickness_property() -> StyledProperty<Thickness> {
175            Border::border_thickness_property().add_owner::<TemplatedControl>()
176        }
177    );
178
179    ferro_property!(
180        /// Defines the `CornerRadius` property.
181        pub fn corner_radius_property() -> StyledProperty<CornerRadius> {
182            Border::corner_radius_property().add_owner::<TemplatedControl>()
183        }
184    );
185
186    ferro_property!(
187        /// Defines the `FontFamily` property.
188        pub fn font_family_property() -> StyledProperty<FontFamily> {
189            TextElement::font_family_property().add_owner::<TemplatedControl>()
190        }
191    );
192
193    ferro_property!(
194        /// Defines the `FontFeatures` property.
195        pub fn font_features_property() -> StyledProperty<Option<FontFeatureCollection>> {
196            TextElement::font_features_property().add_owner::<TemplatedControl>()
197        }
198    );
199
200    ferro_property!(
201        /// Defines the `FontSize` property.
202        pub fn font_size_property() -> StyledProperty<f64> {
203            TextElement::font_size_property().add_owner::<TemplatedControl>()
204        }
205    );
206
207    ferro_property!(
208        /// Defines the `FontStyle` property.
209        pub fn font_style_property() -> StyledProperty<FontStyle> {
210            TextElement::font_style_property().add_owner::<TemplatedControl>()
211        }
212    );
213
214    ferro_property!(
215        /// Defines the `FontWeight` property.
216        pub fn font_weight_property() -> StyledProperty<FontWeight> {
217            TextElement::font_weight_property().add_owner::<TemplatedControl>()
218        }
219    );
220
221    ferro_property!(
222        /// Defines the `FontStretch` property.
223        pub fn font_stretch_property() -> StyledProperty<FontStretch> {
224            TextElement::font_stretch_property().add_owner::<TemplatedControl>()
225        }
226    );
227
228    ferro_property!(
229        /// Defines the `Foreground` property.
230        pub fn foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
231            TextElement::foreground_property().add_owner::<TemplatedControl>()
232        }
233    );
234
235    ferro_property!(
236        /// Defines the `LetterSpacing` property.
237        pub fn letter_spacing_property() -> StyledProperty<f64> {
238            TextElement::letter_spacing_property().add_owner::<TemplatedControl>()
239        }
240    );
241
242    ferro_property!(
243        /// Defines the `Padding` property.
244        pub fn padding_property() -> StyledProperty<Thickness> {
245            Decorator::padding_property().add_owner::<TemplatedControl>()
246        }
247    );
248
249    ferro_property!(
250        /// Defines the `Template` property.
251        pub fn template_property() -> StyledProperty<Option<Rc<dyn IControlTemplate>>> {
252            FerroProperty::register::<TemplatedControl, _>("Template", None)
253        }
254    );
255
256    ferro_property!(
257        /// Defines the `IsTemplateFocusTarget` attached property.
258        pub fn is_template_focus_target_property() -> AttachedProperty<bool> {
259            FerroProperty::register_attached::<TemplatedControl, Control, _>("IsTemplateFocusTarget", false)
260        }
261    );
262} }
263
264impl TemplatedControl {
265    ferro_routed_event!(
266        /// Defines the `TemplateApplied` routed event.
267        pub fn template_applied_event() -> RoutedEvent<TemplateAppliedEventArgs> {
268            RoutedEvent::register::<TemplatedControl, _>("TemplateApplied", RoutingStrategies::DIRECT)
269        }
270    );
271
272    fn static_constructor() {
273        Visual::clip_to_bounds_property().override_default_value::<TemplatedControl>(true);
274        Self::template_property().changed().add_class_handler::<TemplatedControl>(|x, e| x.on_template_changed(e));
275    }
276
277    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
278    pub fn construct() -> Self {
279        Self { base: Control::construct(), applied_template: RefCell::new(None) }
280    }
281
282    pub fn new() -> Ref<Self> {
283        instantiate(Self::construct())
284    }
285
286    /// Raised when the control's template is applied.
287    pub fn template_applied(
288        &self,
289        handler: impl Fn(&Interactive, &TemplateAppliedEventArgs) + 'static,
290    ) -> RoutedEventHandlerToken {
291        self.add_handler(Self::template_applied_event(), handler)
292    }
293
294    /// The brush used to draw the control's background.
295    pub fn background(&self) -> Option<Rc<dyn IBrush>> {
296        self.get_value(Self::background_property())
297    }
298
299    pub fn set_background(&self, value: Option<Rc<dyn IBrush>>) {
300        self.set_value(Self::background_property(), value)
301    }
302
303    /// How the control's background is drawn relative to the control's
304    /// border.
305    pub fn background_sizing(&self) -> BackgroundSizing {
306        self.get_value(Self::background_sizing_property())
307    }
308
309    pub fn set_background_sizing(&self, value: BackgroundSizing) {
310        self.set_value(Self::background_sizing_property(), value)
311    }
312
313    /// The brush used to draw the control's border.
314    pub fn border_brush(&self) -> Option<Rc<dyn IBrush>> {
315        self.get_value(Self::border_brush_property())
316    }
317
318    pub fn set_border_brush(&self, value: Option<Rc<dyn IBrush>>) {
319        self.set_value(Self::border_brush_property(), value)
320    }
321
322    /// The thickness of the control's border.
323    pub fn border_thickness(&self) -> Thickness {
324        self.get_value(Self::border_thickness_property())
325    }
326
327    pub fn set_border_thickness(&self, value: Thickness) {
328        self.set_value(Self::border_thickness_property(), value)
329    }
330
331    /// The radius of the border rounded corners.
332    pub fn corner_radius(&self) -> CornerRadius {
333        self.get_value(Self::corner_radius_property())
334    }
335
336    pub fn set_corner_radius(&self, value: CornerRadius) {
337        self.set_value(Self::corner_radius_property(), value)
338    }
339
340    /// The font family used to draw the control's text.
341    pub fn font_family(&self) -> FontFamily {
342        self.get_value(Self::font_family_property())
343    }
344
345    pub fn set_font_family(&self, value: FontFamily) {
346        self.set_value(Self::font_family_property(), value)
347    }
348
349    /// The font features turned on/off.
350    pub fn font_features(&self) -> Option<FontFeatureCollection> {
351        self.get_value(Self::font_features_property())
352    }
353
354    pub fn set_font_features(&self, value: Option<FontFeatureCollection>) {
355        self.set_value(Self::font_features_property(), value)
356    }
357
358    /// The size of the control's text in points.
359    pub fn font_size(&self) -> f64 {
360        self.get_value(Self::font_size_property())
361    }
362
363    pub fn set_font_size(&self, value: f64) {
364        self.set_value(Self::font_size_property(), value)
365    }
366
367    /// The font style used to draw the control's text.
368    pub fn font_style(&self) -> FontStyle {
369        self.get_value(Self::font_style_property())
370    }
371
372    pub fn set_font_style(&self, value: FontStyle) {
373        self.set_value(Self::font_style_property(), value)
374    }
375
376    /// The font weight used to draw the control's text.
377    pub fn font_weight(&self) -> FontWeight {
378        self.get_value(Self::font_weight_property())
379    }
380
381    pub fn set_font_weight(&self, value: FontWeight) {
382        self.set_value(Self::font_weight_property(), value)
383    }
384
385    /// The font stretch used to draw the control's text.
386    pub fn font_stretch(&self) -> FontStretch {
387        self.get_value(Self::font_stretch_property())
388    }
389
390    pub fn set_font_stretch(&self, value: FontStretch) {
391        self.set_value(Self::font_stretch_property(), value)
392    }
393
394    /// The brush used to draw the control's text and other foreground elements.
395    pub fn foreground(&self) -> Option<Rc<dyn IBrush>> {
396        self.get_value(Self::foreground_property())
397    }
398
399    pub fn set_foreground(&self, value: Option<Rc<dyn IBrush>>) {
400        self.set_value(Self::foreground_property(), value)
401    }
402
403    /// The letter spacing for the control's text content.
404    pub fn letter_spacing(&self) -> f64 {
405        self.get_value(Self::letter_spacing_property())
406    }
407
408    pub fn set_letter_spacing(&self, value: f64) {
409        self.set_value(Self::letter_spacing_property(), value)
410    }
411
412    /// The padding placed between the border of the control and its content.
413    pub fn padding(&self) -> Thickness {
414        self.get_value(Self::padding_property())
415    }
416
417    pub fn set_padding(&self, value: Thickness) {
418        self.set_value(Self::padding_property(), value)
419    }
420
421    /// The template that defines the control's appearance.
422    pub fn template(&self) -> Option<Rc<dyn IControlTemplate>> {
423        self.get_value(Self::template_property())
424    }
425
426    pub fn set_template(&self, value: Option<Rc<dyn IControlTemplate>>) {
427        self.set_value(Self::template_property(), value)
428    }
429
430    /// Gets the value of the `IsTemplateFocusTarget` attached property on a
431    /// control.
432    pub fn get_is_template_focus_target(control: &Control) -> bool {
433        control.get_value(Self::is_template_focus_target_property())
434    }
435
436    /// Sets the value of the `IsTemplateFocusTarget` attached property on a
437    /// control.
438    ///
439    /// When a control is navigated to using the keyboard, a focus adorner is
440    /// shown - usually around the control itself. However if the templated
441    /// control has a template child with this attached property set to true,
442    /// the focus adorner is shown around that control instead.
443    pub fn set_is_template_focus_target(control: &Control, value: bool) {
444        control.set_value(Self::is_template_focus_target_property(), value)
445    }
446
447    /// Sets the templated parent of a control and of its logical descendants
448    /// that do not have one yet.
449    pub fn apply_templated_parent(control: &StyledElement, templated_parent: Option<&Ref<FerroObject>>) {
450        control.set_templated_parent(Nullable(templated_parent.cloned()));
451
452        let children = control.logical_children().snapshot();
453
454        for child in children.iter() {
455            if child.templated_parent().is_none() {
456                Self::apply_templated_parent(child, templated_parent);
457            }
458        }
459    }
460}