Skip to main content

ferroui_controls/primitives/
headered_selecting_items_control.rs

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