Skip to main content

ferroui_controls/primitives/
headered_content_control.rs

1use crate::utils::debug_display::append_optional_boxed_value;
2use super::TemplatedControlImpl;
3use crate::metadata::TemplatePartAttribute;
4use crate::presenters::ContentPresenter;
5use crate::templates::IDataTemplate;
6use crate::{ContentControl, ContentControlImpl, ContentControlImplExt, Control, ControlImpl};
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::InteractiveImpl;
9use ferroui_base::layout::LayoutableImpl;
10use ferroui_base::{
11    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, FerroObjectImpl,
12    FerroProperty, FerroPropertyChangedEventArgs, Ref, StaticType, StyledElement, StyledElementImpl, StyledProperty,
13    VisualImpl,
14};
15use std::cell::RefCell;
16use std::rc::Rc;
17
18/// A [`ContentControl`] with a header.
19#[repr(C)]
20pub struct HeaderedContentControl {
21    base: ContentControl,
22    header_presenter: RefCell<Option<Ref<ContentPresenter>>>,
23}
24
25ferro_class!(HeaderedContentControl: ContentControl);
26ferroui_base::ferro_class_info!(HeaderedContentControl { new: HeaderedContentControl::new });
27ferro_impl_classes!(
28    HeaderedContentControl: StyledElementImpl,
29    VisualImpl,
30    LayoutableImpl,
31    InteractiveImpl,
32    InputElementImpl,
33    ControlImpl,
34    TemplatedControlImpl
35);
36
37ferroui_base::ferro_impl_classes!(HeaderedContentControl: FerroObjectImpl);
38
39impl ContentControlImpl for HeaderedContentControl {
40    fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
41        let mut result = Self::parent_register_content_presenter(this, presenter);
42
43        if presenter.name().as_deref() == Some("PART_HeaderPresenter") {
44            *this.header_presenter.borrow_mut() = Some(presenter.to_ref());
45            result = true;
46        }
47
48        result
49    }
50}
51
52impl HeaderedContentControl {
53    /// The named parts expected in the control template, in addition to
54    /// those of the base class.
55    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
56        &[TemplatePartAttribute::new("PART_HeaderPresenter", <ContentPresenter as StaticType>::TYPE)];
57}
58
59ferroui_base::ferro_properties! { impl HeaderedContentControl {
60    ferro_property!(
61        /// Defines the `Header` property.
62        pub fn header_property() -> StyledProperty<Option<BoxedValue>> {
63            FerroProperty::register::<HeaderedContentControl, _>("Header", None)
64        }
65    );
66
67    ferro_property!(
68        /// Defines the `HeaderTemplate` property.
69        pub fn header_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
70            FerroProperty::register::<HeaderedContentControl, _>("HeaderTemplate", None)
71        }
72    );
73} }
74
75impl HeaderedContentControl {
76    fn static_constructor() {
77        Self::header_property().changed().add_class_handler::<HeaderedContentControl>(|x, e| x.header_changed(e));
78        crate::i_headered::register_headered::<HeaderedContentControl>();
79    }
80
81    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
82    pub fn construct() -> Self {
83        Self { base: ContentControl::construct(), header_presenter: RefCell::new(None) }
84    }
85
86    pub fn new() -> Ref<Self> {
87        instantiate(Self::construct())
88    }
89
90    /// The header content.
91    pub fn header(&self) -> Option<BoxedValue> {
92        self.get_value(Self::header_property())
93    }
94
95    pub fn set_header(&self, value: Option<BoxedValue>) {
96        self.set_value(Self::header_property(), value)
97    }
98
99    /// The header presenter from the control's template.
100    pub fn header_presenter(&self) -> Option<Ref<ContentPresenter>> {
101        self.header_presenter.borrow().clone()
102    }
103
104    /// The data template used to display the header content of the control.
105    pub fn header_template(&self) -> Option<Rc<dyn IDataTemplate>> {
106        self.get_value(Self::header_template_property())
107    }
108
109    pub fn set_header_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
110        self.set_value(Self::header_template_property(), value)
111    }
112
113    fn header_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
114        let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
115
116        if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
117            StyledElement::logical_children(self).remove(&old_child);
118        }
119
120        if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
121            StyledElement::logical_children(self).add(new_child);
122        }
123    }
124}
125
126impl crate::i_headered::IHeadered for HeaderedContentControl {
127    fn header(&self) -> Option<BoxedValue> {
128        HeaderedContentControl::header(self)
129    }
130
131    fn set_header(&self, value: Option<BoxedValue>) {
132        HeaderedContentControl::set_header(self, value)
133    }
134}
135
136impl HeaderedContentControl {
137    /// Appends the text that describes the control in diagnostics: the description of the
138    /// content control and, with the content, its header.
139    pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
140        let base: &ContentControl = self;
141        base.build_debug_display(builder, include_content);
142
143        if include_content {
144            append_optional_boxed_value(builder, "Header", self.header().as_ref(), true);
145        }
146    }
147}