Skip to main content

ferroui_controls/
content_control.rs

1use crate::utils::debug_display::{append_optional_boxed_value, build_base_debug_display};
2use crate::metadata::{PseudoClassesAttribute, TemplatePartAttribute};
3use crate::presenters::{register_content_presenter_host, ContentPresenter, IContentPresenterHost};
4use crate::primitives::{TemplatedControl, TemplatedControlImpl};
5use crate::templates::{FuncControlTemplate, FuncTemplateNameScopeExtensions, IControlTemplate, IDataTemplate};
6use crate::{Control, ControlImpl};
7use ferroui_base::collections::FerroList;
8use ferroui_base::data::TemplateBinding;
9use ferroui_base::input::InputElementImpl;
10use ferroui_base::interactivity::InteractiveImpl;
11use ferroui_base::layout::{HorizontalAlignment, LayoutableImpl, VerticalAlignment};
12use ferroui_base::{
13    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
14    FerroProperty, FerroPropertyChangedEventArgs, Ref, StaticType, StyledElement, StyledElementImpl, StyledProperty,
15    VisualImpl,
16};
17use std::cell::RefCell;
18use std::rc::Rc;
19
20const PC_EMPTY: &str = ":empty";
21
22/// Displays content according to an [`IDataTemplate`].
23#[repr(C)]
24pub struct ContentControl {
25    base: TemplatedControl,
26    presenter: RefCell<Option<Ref<ContentPresenter>>>,
27}
28
29ferro_class! {
30    ContentControl: TemplatedControl, virtuals ContentControlImpl: TemplatedControlImpl {
31        /// Called when a content presenter whose templated parent is this
32        /// control is created. Returns true if the presenter was registered
33        /// as a presenter of the control.
34        fn register_content_presenter(this, presenter: &ContentPresenter) -> bool;
35    }
36}
37ferroui_base::ferro_class_info!(ContentControl { new: ContentControl::new });
38
39ferro_impl_classes!(
40    ContentControl: StyledElementImpl,
41    VisualImpl,
42    LayoutableImpl,
43    InteractiveImpl,
44    InputElementImpl,
45    ControlImpl,
46    TemplatedControlImpl
47);
48
49ferroui_base::ferro_overrides! { impl FerroObjectImpl for ContentControl {
50    fn constructed(this: &Self) {
51        Self::parent_constructed(this);
52        this.update_pseudo_classes();
53    }
54
55    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
56        Self::parent_on_property_changed(this, change);
57
58        if change.property() == Self::content_property().as_property() {
59            this.content_changed(change);
60        }
61    }
62} }
63
64impl ContentControlImpl for ContentControl {
65    fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
66        if presenter.name().as_deref() == Some("PART_ContentPresenter") {
67            *this.presenter.borrow_mut() = Some(presenter.to_ref());
68            return true;
69        }
70
71        false
72    }
73}
74
75impl IContentPresenterHost for ContentControl {
76    fn logical_children(&self) -> &FerroList<Ref<StyledElement>> {
77        StyledElement::logical_children(self)
78    }
79
80    fn register_content_presenter(&self, presenter: &ContentPresenter) -> bool {
81        ContentControl::register_content_presenter(self, presenter)
82    }
83}
84
85impl ContentControl {
86    /// The named parts expected in the control template.
87    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
88        &[TemplatePartAttribute::new("PART_ContentPresenter", <ContentPresenter as StaticType>::TYPE)];
89
90    /// The pseudoclasses set by the class.
91    pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[PC_EMPTY]);
92}
93
94impl crate::i_content_control::IContentControl for ContentControl {
95    fn content(&self) -> Option<BoxedValue> {
96        ContentControl::content(self)
97    }
98
99    fn set_content(&self, value: Option<BoxedValue>) {
100        ContentControl::set_content(self, value)
101    }
102
103    fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
104        ContentControl::content_template(self)
105    }
106
107    fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
108        ContentControl::set_content_template(self, value)
109    }
110
111    fn horizontal_content_alignment(&self) -> HorizontalAlignment {
112        ContentControl::horizontal_content_alignment(self)
113    }
114
115    fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
116        ContentControl::set_horizontal_content_alignment(self, value)
117    }
118
119    fn vertical_content_alignment(&self) -> VerticalAlignment {
120        ContentControl::vertical_content_alignment(self)
121    }
122
123    fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
124        ContentControl::set_vertical_content_alignment(self, value)
125    }
126}
127
128ferroui_base::ferro_properties! { impl ContentControl {
129    ferro_property!(
130        /// Defines the `Content` property.
131        pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
132            FerroProperty::register::<ContentControl, _>("Content", None)
133        }
134    );
135
136    ferro_property!(
137        /// Defines the `ContentTemplate` property.
138        pub fn content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
139            FerroProperty::register::<ContentControl, _>("ContentTemplate", None)
140        }
141    );
142
143    ferro_property!(
144        /// Defines the `HorizontalContentAlignment` property.
145        pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
146            FerroProperty::register::<ContentControl, _>("HorizontalContentAlignment", HorizontalAlignment::Stretch)
147        }
148    );
149
150    ferro_property!(
151        /// Defines the `VerticalContentAlignment` property.
152        pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
153            FerroProperty::register::<ContentControl, _>("VerticalContentAlignment", VerticalAlignment::Stretch)
154        }
155    );
156} }
157
158impl ContentControl {
159    fn static_constructor() {
160        register_content_presenter_host::<ContentControl>();
161        crate::i_content_control::register_content_control::<ContentControl>();
162
163        let template: Rc<dyn IControlTemplate> = FuncControlTemplate::new(|_, ns| {
164            let presenter = ContentPresenter::new();
165            presenter.set_name(Some("PART_ContentPresenter".to_string()));
166
167            let bind = |property: &'static FerroProperty| {
168                presenter.bind_binding(property, &TemplateBinding::new(property));
169            };
170            bind(TemplatedControl::background_property().as_property());
171            bind(TemplatedControl::background_sizing_property().as_property());
172            bind(TemplatedControl::border_brush_property().as_property());
173            bind(TemplatedControl::border_thickness_property().as_property());
174            bind(TemplatedControl::corner_radius_property().as_property());
175            bind(ContentControl::content_template_property().as_property());
176            bind(ContentControl::content_property().as_property());
177            bind(TemplatedControl::padding_property().as_property());
178            bind(ContentControl::vertical_content_alignment_property().as_property());
179            bind(ContentControl::horizontal_content_alignment_property().as_property());
180
181            presenter.register_in_name_scope(&**ns).upcast()
182        });
183        TemplatedControl::template_property().override_default_value::<ContentControl>(Some(template));
184    }
185
186    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
187    pub fn construct() -> Self {
188        Self { base: TemplatedControl::construct(), presenter: RefCell::new(None) }
189    }
190
191    pub fn new() -> Ref<Self> {
192        instantiate(Self::construct())
193    }
194
195    /// The content to display.
196    pub fn content(&self) -> Option<BoxedValue> {
197        self.get_value(Self::content_property())
198    }
199
200    pub fn set_content(&self, value: Option<BoxedValue>) {
201        self.set_value(Self::content_property(), value)
202    }
203
204    /// The data template used to display the content of the control.
205    pub fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
206        self.get_value(Self::content_template_property())
207    }
208
209    pub fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
210        self.set_value(Self::content_template_property(), value)
211    }
212
213    /// The presenter from the control's template.
214    pub fn presenter(&self) -> Option<Ref<ContentPresenter>> {
215        self.presenter.borrow().clone()
216    }
217
218    /// The horizontal alignment of the content within the control.
219    pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
220        self.get_value(Self::horizontal_content_alignment_property())
221    }
222
223    pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
224        self.set_value(Self::horizontal_content_alignment_property(), value)
225    }
226
227    /// The vertical alignment of the content within the control.
228    pub fn vertical_content_alignment(&self) -> VerticalAlignment {
229        self.get_value(Self::vertical_content_alignment_property())
230    }
231
232    pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
233        self.set_value(Self::vertical_content_alignment_property(), value)
234    }
235
236    fn content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
237        let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
238
239        if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
240            StyledElement::logical_children(self).remove(&old_child);
241        }
242
243        if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
244            StyledElement::logical_children(self).add(new_child);
245        }
246
247        self.update_pseudo_classes();
248    }
249
250    fn update_pseudo_classes(&self) {
251        self.pseudo_classes().set(PC_EMPTY, self.content().is_none());
252    }
253}
254
255impl ContentControl {
256    /// Appends the text that describes the control in diagnostics: the base description and,
257    /// with the content, its content.
258    pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
259        build_base_debug_display(self, builder);
260
261        if include_content {
262            append_optional_boxed_value(builder, "Content", self.content().as_ref(), include_content);
263        }
264    }
265}