Skip to main content

ferroui_controls/primitives/
headered_items_control.rs

1use super::{HeaderedContentControl, TemplatedControlImpl};
2use crate::metadata::TemplatePartAttribute;
3use crate::presenters::{register_content_presenter_host, ContentPresenter, IContentPresenterHost};
4use crate::templates::IDataTemplate;
5use crate::{Control, ControlImpl, ItemsControl, ItemsControlImpl};
6use ferroui_base::collections::FerroList;
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::InteractiveImpl;
9use ferroui_base::layout::LayoutableImpl;
10use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
11use ferroui_base::reactive::IDisposable;
12use ferroui_base::{
13    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, FerroObjectImpl,
14    FerroProperty, FerroPropertyChangedEventArgs, Ref, StaticType, StyledElement, StyledElementImpl,
15    StyledElementImplExt, StyledProperty, VisualImpl, WeakRef,
16};
17use std::cell::RefCell;
18use std::rc::Rc;
19
20/// Represents an [`ItemsControl`] with a related header.
21#[repr(C)]
22pub struct HeaderedItemsControl {
23    base: ItemsControl,
24    items_binding: RefCell<Option<Rc<dyn IDisposable>>>,
25    prepare_item_container_on_attach: RefCell<Option<WeakRef<ItemsControl>>>,
26    header_presenter: RefCell<Option<Ref<ContentPresenter>>>,
27}
28
29ferro_class! {
30    HeaderedItemsControl: ItemsControl, virtuals HeaderedItemsControlImpl: ItemsControlImpl {
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!(HeaderedItemsControl { new: HeaderedItemsControl::new });
38
39ferro_impl_classes!(
40    HeaderedItemsControl: VisualImpl,
41    LayoutableImpl,
42    InteractiveImpl,
43    InputElementImpl,
44    ControlImpl,
45    TemplatedControlImpl,
46    ItemsControlImpl
47);
48
49ferroui_base::ferro_impl_classes!(HeaderedItemsControl: FerroObjectImpl);
50
51impl StyledElementImpl for HeaderedItemsControl {
52    fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
53        Self::parent_on_attached_to_logical_tree(this, e);
54
55        let pending = this.prepare_item_container_on_attach.take();
56        if let Some(parent) = pending.and_then(|parent| parent.upgrade()) {
57            this.prepare_item_container(&parent);
58        }
59    }
60}
61
62impl HeaderedItemsControlImpl for HeaderedItemsControl {
63    fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
64        if presenter.name().as_deref() == Some("PART_HeaderPresenter") {
65            *this.header_presenter.borrow_mut() = Some(presenter.to_ref());
66            return true;
67        }
68
69        false
70    }
71}
72
73impl IContentPresenterHost for HeaderedItemsControl {
74    fn logical_children(&self) -> &FerroList<Ref<StyledElement>> {
75        StyledElement::logical_children(self)
76    }
77
78    fn register_content_presenter(&self, presenter: &ContentPresenter) -> bool {
79        HeaderedItemsControl::register_content_presenter(self, presenter)
80    }
81}
82
83impl HeaderedItemsControl {
84    /// The named parts expected in the control template.
85    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
86        &[TemplatePartAttribute::new("PART_HeaderPresenter", <ContentPresenter as StaticType>::TYPE)];
87}
88
89ferroui_base::ferro_properties! { impl HeaderedItemsControl {
90    ferro_property!(
91        /// Defines the `Header` property.
92        pub fn header_property() -> StyledProperty<Option<BoxedValue>> {
93            HeaderedContentControl::header_property().add_owner::<HeaderedItemsControl>()
94        }
95    );
96
97    ferro_property!(
98        /// Defines the `HeaderTemplate` property.
99        pub fn header_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
100            FerroProperty::register::<HeaderedItemsControl, _>("HeaderTemplate", None)
101        }
102    );
103} }
104
105impl HeaderedItemsControl {
106    fn static_constructor() {
107        register_content_presenter_host::<HeaderedItemsControl>();
108        Self::header_property().changed().add_class_handler::<HeaderedItemsControl>(|x, e| x.header_changed(e));
109    }
110
111    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
112    pub fn construct() -> Self {
113        Self {
114            base: ItemsControl::construct(),
115            items_binding: RefCell::new(None),
116            prepare_item_container_on_attach: RefCell::new(None),
117            header_presenter: RefCell::new(None),
118        }
119    }
120
121    pub fn new() -> Ref<Self> {
122        instantiate(Self::construct())
123    }
124
125    /// Gets or sets the content of the control's header.
126    pub fn header(&self) -> Option<BoxedValue> {
127        self.get_value(Self::header_property())
128    }
129
130    pub fn set_header(&self, value: Option<BoxedValue>) {
131        self.set_value(Self::header_property(), value)
132    }
133
134    /// Gets or sets the data template used to display the header content of
135    /// the control.
136    pub fn header_template(&self) -> Option<Rc<dyn IDataTemplate>> {
137        self.get_value(Self::header_template_property())
138    }
139
140    pub fn set_header_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
141        self.set_value(Self::header_template_property(), value)
142    }
143
144    /// Gets the header presenter from the control's template.
145    pub fn header_presenter(&self) -> Option<Ref<ContentPresenter>> {
146        self.header_presenter.borrow().clone()
147    }
148
149    pub(crate) fn prepare_item_container(&self, parent: &ItemsControl) {
150        if let Some(items_binding) = self.items_binding.take() {
151            items_binding.dispose();
152        }
153
154        let Some(item) = self.header() else {
155            *self.prepare_item_container_on_attach.borrow_mut() = None;
156            return;
157        };
158
159        let mut header_template = self.header_template().or_else(|| parent.item_template());
160
161        if header_template.is_none() {
162            if self.is_attached_to_logical_tree() {
163                header_template = self.find_data_template(Some(&item), None);
164            } else {
165                *self.prepare_item_container_on_attach.borrow_mut() = Some(parent.to_ref().downgrade());
166            }
167        }
168
169        if let Some(tree_template) = header_template.as_ref().and_then(|t| t.as_tree_data_template()) {
170            if tree_template.match_(Some(&item)) {
171                let items_binding =
172                    tree_template.bind_children(self, ItemsControl::items_source_property().as_property(), &item);
173                *self.items_binding.borrow_mut() = Some(items_binding);
174            }
175        }
176    }
177
178    fn header_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
179        let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
180
181        if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
182            StyledElement::logical_children(self).remove(&old_child);
183        }
184
185        if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
186            StyledElement::logical_children(self).add(new_child);
187        }
188    }
189}