Skip to main content

ferroui_controls/
items_control.rs

1use crate::assigned_binding::AssignedBinding;
2use crate::generators::{ItemContainerGenerator, RecycleKey};
3use crate::items_source::{ItemsChangedEventArgs, ItemsSource, ItemsView};
4use crate::metadata::PseudoClassesAttribute;
5use crate::navigable_containers::{as_navigable_container, register_navigable_container};
6use crate::presenters::{ContentPresenter, ItemsPresenter};
7use crate::primitives::{
8    HeaderedContentControl, HeaderedItemsControl, HeaderedSelectingItemsControl, TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl,
9    TemplatedControlImplExt,
10};
11use crate::templates::{FuncDataTemplate, FuncTemplate, IDataTemplate, ITemplateOf};
12use crate::{
13    Canvas, ContainerClearingEventArgs, ContainerIndexChangedEventArgs, ContainerPreparedEventArgs, ContentControl,
14    Control, ControlImpl, ItemCollection, ItemsSourceView, Panel, StackPanel, TextBlock, VirtualizingPanel, WrapPanel,
15};
16use ferroui_base::collections::NotifyCollectionChangedAction;
17use ferroui_base::input::{
18    FocusChangedEventArgs, FocusManager, INavigableContainer, InputElement, InputElementImpl, InputElementImplExt,
19    KeyEventArgs,
20    KeyModifiers, KeyboardNavigation, NavigationDirection, NavigationMethod,
21};
22use ferroui_base::interactivity::InteractiveImpl;
23use ferroui_base::layout::LayoutableImpl;
24use ferroui_base::logical_tree::{ChildIndexChangedEventArgs, IChildIndexProvider};
25use ferroui_base::reactive::{Disposable, IDisposable};
26use ferroui_base::styling::ControlTheme;
27use ferroui_base::utilities::HandlerList;
28use ferroui_base::{
29    ferro_class, ferro_impl_classes, ferro_property, instantiate, AnyValue, AttachedProperty, BoxedValue,
30    DirectProperty, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs,
31    ObjectType, PropertyValue, Ref, StyledElement, StyledElementImpl, StyledProperty, Upcast, Visual, VisualImpl,
32    WeakRef,
33};
34use std::cell::{Cell, OnceCell, RefCell};
35use std::rc::Rc;
36
37type ChildIndexChangedHandlers = HandlerList<dyn Fn(&ChildIndexChangedEventArgs)>;
38
39/// Displays a collection of items.
40#[repr(C)]
41pub struct ItemsControl {
42    base: TemplatedControl,
43    items: ItemCollection,
44    item_count: Cell<i32>,
45    item_container_generator: OnceCell<Rc<ItemContainerGenerator>>,
46    child_index_changed: Rc<ChildIndexChangedHandlers>,
47    child_index_provider: OnceCell<Rc<ItemsControlChildIndexProvider>>,
48    display_member_item_template: RefCell<Option<Rc<dyn IDataTemplate>>>,
49    items_presenter: RefCell<Option<Ref<ItemsPresenter>>>,
50    presenter: RefCell<Option<Ref<ItemsPresenter>>>,
51    wrap_focus: Cell<bool>,
52    preparing_container: HandlerList<dyn Fn(&ContainerPreparedEventArgs)>,
53    container_prepared: HandlerList<dyn Fn(&ContainerPreparedEventArgs)>,
54    container_index_changed: HandlerList<dyn Fn(&ContainerIndexChangedEventArgs)>,
55    container_clearing: HandlerList<dyn Fn(&ContainerClearingEventArgs)>,
56}
57
58ferro_class! {
59    ItemsControl: TemplatedControl, virtuals ItemsControlImpl: TemplatedControlImpl {
60        /// Creates or a container that can be used to display an item.
61        fn create_container_for_item_override(
62            this,
63            item: &Option<BoxedValue>,
64            index: i32,
65            recycle_key: Option<RecycleKey>
66        ) -> Ref<Control>;
67        /// Prepares the specified element to display the specified item.
68        fn prepare_container_for_item_override(this, container: &Ref<Control>, item: &Option<BoxedValue>, index: i32);
69        /// Called when a container has been fully prepared to display an
70        /// item.
71        ///
72        /// This method will be called when a container has been fully
73        /// prepared and added to the logical and visual trees, but may be
74        /// called before a layout pass has completed. It is called
75        /// immediately before the `container_prepared` event is raised.
76        fn container_for_item_prepared_override(this, container: &Ref<Control>, item: &Option<BoxedValue>, index: i32);
77        /// Called when the index for a container changes due to an
78        /// insertion or removal in the items collection.
79        fn container_index_changed_override(this, container: &Ref<Control>, old_index: i32, new_index: i32);
80        /// Undoes the effects of the `prepare_container_for_item_override`
81        /// method.
82        fn clear_container_for_item_override(this, container: &Ref<Control>);
83        /// Determines whether the specified item can be its own container.
84        ///
85        /// Returns true if the item needs a container; otherwise false if
86        /// the item can itself be used as a container. The second value is
87        /// a key that can be used to locate a previously recycled container
88        /// of the correct type, or `None` if the item cannot be recycled.
89        /// If the item is its own container then by definition it cannot be
90        /// recycled, so the key should be `None`.
91        fn needs_container_override(this, item: &Option<BoxedValue>, index: i32) -> (bool, Option<RecycleKey>);
92        /// Called when the collection changed event is raised on the items
93        /// view.
94        fn on_items_view_collection_changed(this, e: &ItemsChangedEventArgs<'_>);
95    }
96}
97ferroui_base::ferro_class_info!(ItemsControl { new: ItemsControl::new });
98
99ferro_impl_classes!(ItemsControl: VisualImpl, LayoutableImpl, InteractiveImpl);
100
101impl ControlImpl for ItemsControl {
102    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
103        crate::automation::peers::ItemsControlAutomationPeer::new(this).upcast()
104    }
105}
106
107impl FerroObjectImpl for ItemsControl {
108    fn constructed(this: &Self) {
109        Self::parent_constructed(this);
110
111        this.update_pseudo_classes();
112
113        let weak = this.to_ref().downgrade();
114        this.items.add_collection_changed(Rc::new(move |e: &ItemsChangedEventArgs<'_>| {
115            if let Some(this) = weak.upgrade() {
116                this.on_items_view_collection_changed(e);
117            }
118        }));
119    }
120
121    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
122        Self::parent_on_property_changed(this, change);
123
124        if change.property() == Self::item_container_theme_property().as_property()
125            && this.item_container_generator.get().is_some()
126        {
127            this.refresh_containers();
128        } else if change.property() == Self::items_source_property().as_property() {
129            this.items.set_items_source(change.get_new_value::<Option<ItemsSource>>());
130        } else if change.property() == Self::item_template_property().as_property() {
131            if change.get_new_value::<Option<Rc<dyn IDataTemplate>>>().is_some()
132                && this.display_member_binding().is_some()
133            {
134                panic!("Cannot set both DisplayMemberBinding and ItemTemplate.");
135            }
136            this.refresh_containers();
137        } else if change.property() == Self::display_member_binding_property().as_property() {
138            if change.get_new_value::<Option<AssignedBinding>>().is_some() && this.item_template().is_some() {
139                panic!("Cannot set both DisplayMemberBinding and ItemTemplate.");
140            }
141            *this.display_member_item_template.borrow_mut() = None;
142            this.refresh_containers();
143        }
144    }
145}
146
147impl StyledElementImpl for ItemsControl {
148    fn child_index_provider(this: &Self) -> Option<Rc<dyn IChildIndexProvider>> {
149        let provider = this
150            .child_index_provider
151            .get_or_init(|| Rc::new(ItemsControlChildIndexProvider { owner: this.to_ref().downgrade() }))
152            .clone();
153        Some(provider)
154    }
155}
156
157impl InputElementImpl for ItemsControl {
158    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
159        Self::parent_on_got_focus(this, e);
160
161        // If the focus is coming from a child control, set the tab once
162        // active element to the focused control. This ensures that tabbing
163        // back into the control will focus the last focused control when
164        // the tab navigation mode is `Once`.
165        if !e.is_source(this) {
166            if let Some(ie) = e.source().and_then(|source| source.cast::<InputElement>()) {
167                KeyboardNavigation::set_tab_once_active_element(this, Some(ie));
168            }
169        }
170    }
171
172    /// Handles directional navigation within the [`ItemsControl`].
173    fn on_key_down(this: &Self, e: &KeyEventArgs) {
174        if !e.handled() {
175            let focus = FocusManager::get_focus_manager(this);
176            let direction = e.key.to_navigation_direction(KeyModifiers::NONE);
177            let panel = this.presenter().and_then(|presenter| presenter.panel());
178            let container = panel.as_ref().and_then(|panel| as_navigable_container(panel));
179            let focused = focus.as_ref().and_then(|focus| focus.get_focused_element());
180
181            let (Some(container), Some(focused), Some(direction), Some(panel)) =
182                (container, focused, direction, panel.as_ref())
183            else {
184                return;
185            };
186
187            if direction.is_tab() {
188                return;
189            }
190
191            let panel_visual: Ref<Visual> = panel.clone().upcast();
192            let mut current: Option<Ref<Visual>> = Some(focused.upcast());
193
194            while let Some(visual) = current {
195                let parent = visual.visual_parent();
196
197                if parent.as_ref() == Some(&panel_visual) {
198                    if let Some(input_element) = visual.cast::<InputElement>() {
199                        let next = Self::get_next_control(container, direction, Some(&input_element), this.wrap_focus());
200
201                        if let Some(next) = next {
202                            next.focus_with(NavigationMethod::Directional, e.key_modifiers);
203                            e.set_handled(true);
204                        }
205
206                        break;
207                    }
208                }
209
210                current = parent;
211            }
212        }
213
214        Self::parent_on_key_down(this, e);
215    }
216}
217
218impl TemplatedControlImpl for ItemsControl {
219    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
220        Self::parent_on_apply_template(this, e);
221        *this.items_presenter.borrow_mut() = e.name_scope().find_as::<ItemsPresenter>("PART_ItemsPresenter");
222    }
223}
224
225impl ItemsControlImpl for ItemsControl {
226    fn create_container_for_item_override(
227        _this: &Self,
228        _item: &Option<BoxedValue>,
229        _index: i32,
230        _recycle_key: Option<RecycleKey>,
231    ) -> Ref<Control> {
232        ContentPresenter::new().upcast()
233    }
234
235    fn prepare_container_for_item_override(
236        this: &Self,
237        container: &Ref<Control>,
238        item: &Option<BoxedValue>,
239        _index: i32,
240    ) {
241        let item_control = item.as_ref().and_then(Control::from_boxed);
242
243        if item_control.as_ref() == Some(container) {
244            return;
245        }
246
247        let item_template = this.get_effective_item_template();
248
249        if let Some(hcc) = container.downcast_ref::<HeaderedContentControl>() {
250            Self::set_if_unset(hcc, ContentControl::content_property(), item.clone());
251
252            // An item with a header: a headered content control.
253            let headered = item_control.as_ref().and_then(|c| crate::i_headered::as_headered(c));
254            if let Some(headered) = headered {
255                Self::set_if_unset(hcc, HeaderedContentControl::header_property(), headered.header());
256            } else if !Self::is_visual(item) {
257                Self::set_if_unset(hcc, HeaderedContentControl::header_property(), item.clone());
258            }
259
260            if let Some(item_template) = &item_template {
261                Self::set_if_unset(
262                    hcc,
263                    HeaderedContentControl::header_template_property(),
264                    Some(item_template.clone()),
265                );
266            }
267        } else if let Some(cc) = container.downcast_ref::<ContentControl>() {
268            Self::set_if_unset(cc, ContentControl::content_property(), item.clone());
269            if let Some(item_template) = &item_template {
270                Self::set_if_unset(cc, ContentControl::content_template_property(), Some(item_template.clone()));
271            }
272        } else if let Some(p) = container.downcast_ref::<ContentPresenter>() {
273            Self::set_if_unset(p, ContentPresenter::content_property(), item.clone());
274            if let Some(item_template) = &item_template {
275                Self::set_if_unset(p, ContentPresenter::content_template_property(), Some(item_template.clone()));
276            }
277        } else if let Some(ic) = container.downcast_ref::<ItemsControl>() {
278            if let Some(item_template) = &item_template {
279                Self::set_if_unset(ic, Self::item_template_property(), Some(item_template.clone()));
280            }
281            if let Some(ict) = this.item_container_theme() {
282                Self::set_if_unset(ic, Self::item_container_theme_property(), Some(ict));
283            }
284        }
285
286        // These conditions are separate because the headered items controls
287        // also need to run the items control preparation.
288        if let Some(hic) = container.downcast_ref::<HeaderedItemsControl>() {
289            Self::set_if_unset(hic, HeaderedItemsControl::header_property(), item.clone());
290            Self::set_if_unset(hic, HeaderedItemsControl::header_template_property(), item_template.clone());
291            hic.prepare_item_container(this);
292        } else if let Some(hsic) = container.downcast_ref::<HeaderedSelectingItemsControl>() {
293            Self::set_if_unset(hsic, HeaderedSelectingItemsControl::header_property(), item.clone());
294            Self::set_if_unset(hsic, HeaderedSelectingItemsControl::header_template_property(), item_template.clone());
295            hsic.prepare_item_container(this);
296        }
297    }
298
299    fn container_for_item_prepared_override(
300        _this: &Self,
301        _container: &Ref<Control>,
302        _item: &Option<BoxedValue>,
303        _index: i32,
304    ) {
305    }
306
307    fn container_index_changed_override(_this: &Self, _container: &Ref<Control>, _old_index: i32, _new_index: i32) {}
308
309    fn clear_container_for_item_override(_this: &Self, container: &Ref<Control>) {
310        if let Some(hcc) = container.downcast_ref::<HeaderedContentControl>() {
311            hcc.clear_value(ContentControl::content_property());
312            hcc.clear_value(HeaderedContentControl::header_property());
313            hcc.clear_value(HeaderedContentControl::header_template_property());
314        } else if let Some(cc) = container.downcast_ref::<ContentControl>() {
315            cc.clear_value(ContentControl::content_property());
316            cc.clear_value(ContentControl::content_template_property());
317        } else if let Some(p) = container.downcast_ref::<ContentPresenter>() {
318            p.clear_value(ContentPresenter::content_property());
319            p.clear_value(ContentPresenter::content_template_property());
320        } else if let Some(ic) = container.downcast_ref::<ItemsControl>() {
321            ic.clear_value(Self::item_template_property());
322            ic.clear_value(Self::item_container_theme_property());
323        }
324
325        if let Some(hic) = container.downcast_ref::<HeaderedItemsControl>() {
326            hic.clear_value(HeaderedItemsControl::header_property());
327            hic.clear_value(HeaderedItemsControl::header_template_property());
328        } else if let Some(hsic) = container.downcast_ref::<HeaderedSelectingItemsControl>() {
329            hsic.clear_value(HeaderedSelectingItemsControl::header_property());
330            hsic.clear_value(HeaderedSelectingItemsControl::header_template_property());
331        }
332
333        // Feels like we should be clearing the headered items control's
334        // items binding here, but looking at the WPF source it seems that
335        // this isn't done there.
336    }
337
338    fn needs_container_override(this: &Self, item: &Option<BoxedValue>, _index: i32) -> (bool, Option<RecycleKey>) {
339        this.needs_container::<Control>(item)
340    }
341
342    fn on_items_view_collection_changed(this: &Self, e: &ItemsChangedEventArgs<'_>) {
343        if !this.items.is_read_only() {
344            match e.action {
345                NotifyCollectionChangedAction::Add => this.add_control_items_to_logical_children(e.new_items),
346                NotifyCollectionChangedAction::Remove => this.remove_control_items_from_logical_children(e.old_items),
347                _ => {}
348            }
349        }
350
351        this.set_item_count(this.items_view().count() as i32);
352    }
353}
354
355impl ItemsControl {
356    /// The pseudoclasses set by the class.
357    pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":empty", ":singleitem"]);
358}
359
360ferroui_base::ferro_properties! { impl ItemsControl {
361    ferro_property!(
362        /// Defines the `ItemContainerTheme` property.
363        pub fn item_container_theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
364            FerroProperty::register::<ItemsControl, _>("ItemContainerTheme", None)
365        }
366    );
367
368    ferro_property!(
369        /// Defines the `ItemCount` property.
370        pub fn item_count_property() -> DirectProperty<ItemsControl, i32> {
371            FerroProperty::register_direct::<ItemsControl, _>("ItemCount", |o| o.item_count(), None, 0)
372        }
373    );
374
375    ferro_property!(
376        /// Defines the `ItemsPanel` property.
377        pub fn items_panel_property() -> StyledProperty<Rc<dyn ITemplateOf<Option<Ref<Panel>>>>> {
378            // The default value for the `ItemsPanel` property.
379            let default_panel: Rc<dyn ITemplateOf<Option<Ref<Panel>>>> =
380                FuncTemplate::new(|| Some(StackPanel::new().upcast::<Panel>()));
381            FerroProperty::register::<ItemsControl, _>("ItemsPanel", default_panel)
382        }
383    );
384
385    ferro_property!(
386        /// Defines the `ItemsSource` property.
387        pub fn items_source_property() -> StyledProperty<Option<ItemsSource>> {
388            FerroProperty::register::<ItemsControl, _>("ItemsSource", None)
389        }
390    );
391
392    ferro_property!(
393        /// Defines the `ItemTemplate` property.
394        pub fn item_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
395            FerroProperty::register::<ItemsControl, _>("ItemTemplate", None)
396        }
397    );
398
399    ferro_property!(
400        /// Defines the `DisplayMemberBinding` property.
401        pub fn display_member_binding_property() -> StyledProperty<Option<AssignedBinding>> {
402            FerroProperty::register::<ItemsControl, _>("DisplayMemberBinding", None)
403        }
404    );
405
406    ferro_property!(
407        fn applied_item_container_theme_property() -> AttachedProperty<Option<Ref<ControlTheme>>> {
408            FerroProperty::register_attached::<ItemsControl, Control, _>("AppliedItemContainerTheme", None)
409        }
410    );
411} }
412
413impl ItemsControl {
414    fn static_constructor() {
415        register_navigable_container::<StackPanel>();
416        register_navigable_container::<WrapPanel>();
417        register_navigable_container::<Canvas>();
418        register_navigable_container::<VirtualizingPanel>();
419    }
420
421    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
422    pub fn construct() -> Self {
423        Self {
424            base: TemplatedControl::construct(),
425            items: ItemCollection::new(),
426            item_count: Cell::new(0),
427            item_container_generator: OnceCell::new(),
428            child_index_changed: Rc::new(HandlerList::new()),
429            child_index_provider: OnceCell::new(),
430            display_member_item_template: RefCell::new(None),
431            items_presenter: RefCell::new(None),
432            presenter: RefCell::new(None),
433            wrap_focus: Cell::new(false),
434            preparing_container: HandlerList::new(),
435            container_prepared: HandlerList::new(),
436            container_index_changed: HandlerList::new(),
437            container_clearing: HandlerList::new(),
438        }
439    }
440
441    pub fn new() -> Ref<Self> {
442        instantiate(Self::construct())
443    }
444
445    /// Gets or sets the binding to use for binding to the display member of
446    /// each item.
447    pub fn display_member_binding(&self) -> Option<AssignedBinding> {
448        self.get_value(Self::display_member_binding_property())
449    }
450
451    pub fn set_display_member_binding(&self, value: Option<AssignedBinding>) {
452        self.set_value(Self::display_member_binding_property(), value)
453    }
454
455    /// Gets the [`ItemContainerGenerator`] for the control.
456    pub fn item_container_generator(&self) -> Rc<ItemContainerGenerator> {
457        self.item_container_generator.get_or_init(|| Rc::new(ItemContainerGenerator::new(self))).clone()
458    }
459
460    /// Gets the items to display.
461    ///
462    /// You use either the `items` or the `items_source` property to specify
463    /// the collection that should be used to generate the content of your
464    /// [`ItemsControl`]. When the `items_source` property is set, the
465    /// `items` collection is made read-only and fixed-size.
466    ///
467    /// When `items_source` is in use, setting the `items_source` property
468    /// to `None` removes the collection and restores usage to `items`,
469    /// which will be an empty [`ItemCollection`].
470    pub fn items(&self) -> ItemCollection {
471        self.items.clone()
472    }
473
474    /// Gets or sets the [`ControlTheme`] that is applied to the container
475    /// element generated for each item.
476    pub fn item_container_theme(&self) -> Option<Ref<ControlTheme>> {
477        self.get_value(Self::item_container_theme_property())
478    }
479
480    pub fn set_item_container_theme(&self, value: Option<Ref<ControlTheme>>) {
481        self.set_value(Self::item_container_theme_property(), value)
482    }
483
484    /// Gets the number of items being displayed by the [`ItemsControl`].
485    pub fn item_count(&self) -> i32 {
486        self.item_count.get()
487    }
488
489    fn set_item_count(&self, value: i32) {
490        if self.set_and_raise_cell(Self::item_count_property(), &self.item_count, value) {
491            self.update_pseudo_classes();
492            self.raise_child_index_changed(&ChildIndexChangedEventArgs::total_count_changed());
493        }
494    }
495
496    /// Gets or sets the panel used to display the items.
497    pub fn items_panel(&self) -> Rc<dyn ITemplateOf<Option<Ref<Panel>>>> {
498        self.get_value(Self::items_panel_property())
499    }
500
501    pub fn set_items_panel(&self, value: Rc<dyn ITemplateOf<Option<Ref<Panel>>>>) {
502        self.set_value(Self::items_panel_property(), value)
503    }
504
505    /// Gets or sets a collection used to generate the content of the
506    /// [`ItemsControl`].
507    ///
508    /// A common scenario is to use an [`ItemsControl`] such as a list box
509    /// to display a data collection, or to bind an [`ItemsControl`] to a
510    /// collection object. To bind an [`ItemsControl`] to a collection
511    /// object, use the `items_source` property.
512    ///
513    /// When the `items_source` property is set, the `items` collection is
514    /// made read-only and fixed-size.
515    ///
516    /// When `items_source` is in use, setting the property to `None`
517    /// removes the collection and restores usage to `items`, which will be
518    /// an empty [`ItemCollection`].
519    pub fn items_source(&self) -> Option<ItemsSource> {
520        self.get_value(Self::items_source_property())
521    }
522
523    pub fn set_items_source(&self, value: Option<ItemsSource>) {
524        self.set_value(Self::items_source_property(), value)
525    }
526
527    /// Gets or sets the data template used to display the items in the
528    /// control.
529    pub fn item_template(&self) -> Option<Rc<dyn IDataTemplate>> {
530        self.get_value(Self::item_template_property())
531    }
532
533    pub fn set_item_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
534        self.set_value(Self::item_template_property(), value)
535    }
536
537    /// Gets the items presenter control.
538    pub fn presenter(&self) -> Option<Ref<ItemsPresenter>> {
539        self.presenter.borrow().clone()
540    }
541
542    /// Gets the [`Panel`] specified by `items_panel`.
543    pub fn items_panel_root(&self) -> Option<Ref<Panel>> {
544        self.presenter().and_then(|presenter| presenter.panel())
545    }
546
547    /// Gets a read-only view of the items in the [`ItemsControl`].
548    pub fn items_view(&self) -> &Rc<ItemsSourceView> {
549        self.items.view()
550    }
551
552    /// Whether directional navigation wraps around at the first and last
553    /// items.
554    pub fn wrap_focus(&self) -> bool {
555        self.wrap_focus.get()
556    }
557
558    pub fn set_wrap_focus(&self, value: bool) {
559        self.wrap_focus.set(value)
560    }
561
562    /// Occurs immediately before a container is prepared for use.
563    ///
564    /// The prepared element might be newly created or an existing container
565    /// that is being re-used.
566    pub fn preparing_container(&self, handler: impl Fn(&ContainerPreparedEventArgs) + 'static) -> Rc<dyn IDisposable> {
567        let token = self.preparing_container.add(Rc::new(handler));
568        let weak = self.to_ref().downgrade();
569        Disposable::create(move || {
570            if let Some(this) = weak.upgrade() {
571                this.preparing_container.remove(token);
572            }
573        })
574    }
575
576    /// Occurs each time a container is prepared for use.
577    ///
578    /// The prepared element might be newly created or an existing container
579    /// that is being re-used.
580    pub fn container_prepared(&self, handler: impl Fn(&ContainerPreparedEventArgs) + 'static) -> Rc<dyn IDisposable> {
581        let token = self.container_prepared.add(Rc::new(handler));
582        let weak = self.to_ref().downgrade();
583        Disposable::create(move || {
584            if let Some(this) = weak.upgrade() {
585                this.container_prepared.remove(token);
586            }
587        })
588    }
589
590    /// Occurs for each realized container when the index for the item it
591    /// represents has changed.
592    ///
593    /// This event is raised for each realized container where the index for
594    /// the item it represents has changed. For example, when another item
595    /// is added or removed in the data source, the index for items that
596    /// come after in the ordering will be impacted.
597    pub fn container_index_changed(
598        &self,
599        handler: impl Fn(&ContainerIndexChangedEventArgs) + 'static,
600    ) -> Rc<dyn IDisposable> {
601        let token = self.container_index_changed.add(Rc::new(handler));
602        let weak = self.to_ref().downgrade();
603        Disposable::create(move || {
604            if let Some(this) = weak.upgrade() {
605                this.container_index_changed.remove(token);
606            }
607        })
608    }
609
610    /// Occurs each time a container is cleared.
611    ///
612    /// This event is raised immediately each time an container is cleared,
613    /// such as when it falls outside the range of realized items or the
614    /// corresponding item is removed.
615    pub fn container_clearing(&self, handler: impl Fn(&ContainerClearingEventArgs) + 'static) -> Rc<dyn IDisposable> {
616        let token = self.container_clearing.add(Rc::new(handler));
617        let weak = self.to_ref().downgrade();
618        Disposable::create(move || {
619            if let Some(this) = weak.upgrade() {
620                this.container_clearing.remove(token);
621            }
622        })
623    }
624
625    /// Gets a default recycle key that can be used when an [`ItemsControl`]
626    /// supports a single container type.
627    pub const DEFAULT_RECYCLE_KEY: RecycleKey = RecycleKey::Default;
628
629    /// Returns the container for the item at the specified index.
630    ///
631    /// Returns the container for the item at the specified index within the
632    /// item collection, if the item has a container; otherwise, `None`.
633    pub fn container_from_index(&self, index: i32) -> Option<Ref<Control>> {
634        self.presenter().and_then(|presenter| presenter.container_from_index(index))
635    }
636
637    /// Returns the container corresponding to the specified item.
638    ///
639    /// Returns a container that corresponds to the specified item, if the
640    /// item has a container and exists in the collection; otherwise,
641    /// `None`.
642    pub fn container_from_item(&self, item: &Option<BoxedValue>) -> Option<Ref<Control>> {
643        let index = self.items.index_of(item);
644        if index >= 0 {
645            self.container_from_index(index)
646        } else {
647            None
648        }
649    }
650
651    /// Returns the index to the item that has the specified, generated
652    /// container.
653    ///
654    /// Returns the index to the item that corresponds to the specified
655    /// generated container, or -1 if `container` is not found.
656    pub fn index_from_container(&self, container: &Ref<Control>) -> i32 {
657        self.presenter().map_or(-1, |presenter| presenter.index_from_container(container))
658    }
659
660    /// Returns the item that corresponds to the specified, generated
661    /// container.
662    ///
663    /// Returns the contained item, or `None` if the container does not
664    /// contain an item.
665    pub fn item_from_container(&self, container: &Ref<Control>) -> Option<BoxedValue> {
666        let index = self.index_from_container(container);
667        if index >= 0 && (index as usize) < self.items.count() {
668            self.items.get_at(index as usize)
669        } else {
670            None
671        }
672    }
673
674    /// Gets the currently realized containers.
675    pub fn get_realized_containers(&self) -> Vec<Ref<Control>> {
676        self.presenter().and_then(|presenter| presenter.get_realized_containers()).unwrap_or_default()
677    }
678
679    /// Scrolls the specified item into view.
680    pub fn scroll_into_view(&self, index: i32) {
681        if let Some(presenter) = self.presenter() {
682            presenter.scroll_into_view(index);
683        }
684    }
685
686    /// Scrolls the specified item into view.
687    pub fn scroll_into_view_item(&self, item: &Option<BoxedValue>) {
688        self.scroll_into_view(self.items_view().index_of(item))
689    }
690
691    /// Returns the [`ItemsControl`] that owns the specified container
692    /// control.
693    ///
694    /// Returns the owning [`ItemsControl`] or `None` if the control is not
695    /// an items container.
696    pub fn items_control_from_item_container(container: &Ref<Control>) -> Option<Ref<ItemsControl>> {
697        let mut c = container.parent().and_then(|parent| parent.cast::<Control>());
698
699        while let Some(control) = c {
700            if let Some(items_control) = control.cast::<ItemsControl>() {
701                return if items_control.index_from_container(container) >= 0 { Some(items_control) } else { None };
702            }
703
704            c = control.parent().and_then(|parent| parent.cast::<Control>());
705        }
706
707        None
708    }
709
710    /// A default implementation of `needs_container_override` that returns
711    /// true and sets the recycle key to
712    /// [`DEFAULT_RECYCLE_KEY`](Self::DEFAULT_RECYCLE_KEY) if the item is
713    /// not a `T`.
714    pub fn needs_container<T: ObjectType + Upcast<Control>>(
715        &self,
716        item: &Option<BoxedValue>,
717    ) -> (bool, Option<RecycleKey>) {
718        let is_t = item.as_ref().and_then(Control::from_boxed).is_some_and(|control| control.is::<T>());
719
720        if is_t {
721            (false, None)
722        } else {
723            (true, Some(Self::DEFAULT_RECYCLE_KEY))
724        }
725    }
726
727    /// Refreshes the containers displayed by the control.
728    ///
729    /// Causes all containers to be unrealized and re-realized.
730    pub fn refresh_containers(&self) {
731        if let Some(presenter) = self.presenter() {
732            presenter.refresh();
733        }
734    }
735
736    pub(crate) fn add_logical_child(&self, c: &Ref<Control>) {
737        let child: Ref<StyledElement> = c.clone().upcast();
738        if !self.logical_children().contains(&child) {
739            self.logical_children().add(child);
740        }
741    }
742
743    pub(crate) fn remove_logical_child(&self, c: &Ref<Control>) {
744        let child: Ref<StyledElement> = c.clone().upcast();
745        self.logical_children().remove(&child);
746    }
747
748    /// Called by [`ItemsPresenter`] to register with the [`ItemsControl`].
749    ///
750    /// Items presenters can be within nested templates or in popups and so
751    /// are not necessarily created immediately when the items control's
752    /// template is instantiated. Instead they register themselves using
753    /// this method.
754    pub(crate) fn register_items_presenter(&self, presenter: &ItemsPresenter) {
755        *self.presenter.borrow_mut() = Some(presenter.to_ref());
756        self.raise_child_index_changed(&ChildIndexChangedEventArgs::child_indexes_reset());
757    }
758
759    pub(crate) fn prepare_item_container(&self, container: &Ref<Control>, item: &Option<BoxedValue>, index: i32) {
760        if !self.preparing_container.is_empty() {
761            let e = ContainerPreparedEventArgs::new(container.clone(), index);
762            for (_, handler) in self.preparing_container.snapshot().iter() {
763                handler(&e);
764            }
765        }
766
767        // If the container has no theme set, or we've already applied our
768        // item container theme (and it hasn't changed since) then we're in
769        // control of the container's theme and may need to update it.
770        if !container.is_set(StyledElement::theme_property().as_property())
771            || container.get_value(Self::applied_item_container_theme_property()) == container.theme()
772        {
773            let item_container_theme = self.item_container_theme();
774            let matches = item_container_theme
775                .as_ref()
776                .and_then(|theme| theme.target_type())
777                .is_some_and(|target_type| target_type.is_assignable_from(container.style_key()));
778
779            if matches {
780                // We have an item container theme and it matches the
781                // container. Set the theme property, and mark the container
782                // as having had the item container theme applied.
783                container.set_current_value(StyledElement::theme_property(), item_container_theme.clone());
784                container.set_value(Self::applied_item_container_theme_property(), item_container_theme);
785            } else {
786                // Otherwise clear the theme and the applied item container
787                // theme property.
788                container.clear_value(StyledElement::theme_property());
789                container.clear_value(Self::applied_item_container_theme_property());
790            }
791        }
792
793        if item.as_ref().and_then(Control::from_boxed).is_none() {
794            container.set_data_context(item.clone());
795        }
796
797        self.prepare_container_for_item_override(container, item, index);
798    }
799
800    pub(crate) fn item_container_prepared(&self, container: &Ref<Control>, item: &Option<BoxedValue>, index: i32) {
801        self.container_for_item_prepared_override(container, item, index);
802        if !self.child_index_changed.is_empty() {
803            self.raise_child_index_changed(&ChildIndexChangedEventArgs::new(container.clone().upcast(), index));
804        }
805        if !self.container_prepared.is_empty() {
806            let e = ContainerPreparedEventArgs::new(container.clone(), index);
807            for (_, handler) in self.container_prepared.snapshot().iter() {
808                handler(&e);
809            }
810        }
811    }
812
813    pub(crate) fn item_container_index_changed(&self, container: &Ref<Control>, old_index: i32, new_index: i32) {
814        self.container_index_changed_override(container, old_index, new_index);
815        if !self.child_index_changed.is_empty() {
816            self.raise_child_index_changed(&ChildIndexChangedEventArgs::new(container.clone().upcast(), new_index));
817        }
818        if !self.container_index_changed.is_empty() {
819            let e = ContainerIndexChangedEventArgs::new(container.clone(), old_index, new_index);
820            for (_, handler) in self.container_index_changed.snapshot().iter() {
821                handler(&e);
822            }
823        }
824    }
825
826    pub(crate) fn clear_item_container(&self, container: &Ref<Control>) {
827        self.clear_container_for_item_override(container);
828        if !self.container_clearing.is_empty() {
829            let e = ContainerClearingEventArgs::new(container.clone());
830            for (_, handler) in self.container_clearing.snapshot().iter() {
831                handler(&e);
832            }
833        }
834    }
835
836    fn add_control_items_to_logical_children(&self, items: ItemsView<'_>) {
837        let mut to_add: Option<Vec<Ref<StyledElement>>> = None;
838
839        for i in items {
840            if let Some(control) = i.as_ref().and_then(Control::from_boxed) {
841                let control: Ref<StyledElement> = control.upcast();
842                if !self.logical_children().contains(&control) {
843                    to_add.get_or_insert_with(Vec::new).push(control);
844                }
845            }
846        }
847
848        if let Some(to_add) = to_add {
849            self.logical_children().add_range(to_add);
850        }
851    }
852
853    pub(crate) fn set_if_unset<T: PropertyValue>(target: &FerroObject, property: &'static StyledProperty<T>, value: T) {
854        if !target.is_set(property.as_property()) {
855            target.set_current_value(property, value);
856        }
857    }
858
859    fn remove_control_items_from_logical_children(&self, items: ItemsView<'_>) {
860        let mut to_remove: Option<Vec<Ref<StyledElement>>> = None;
861
862        for i in items {
863            if let Some(control) = i.as_ref().and_then(Control::from_boxed) {
864                to_remove.get_or_insert_with(Vec::new).push(control.upcast());
865            }
866        }
867
868        if let Some(to_remove) = to_remove {
869            self.logical_children().remove_all(to_remove);
870        }
871    }
872
873    /// The template used to display the items: the item template, or the
874    /// template created for the display member binding.
875    pub(crate) fn get_effective_item_template(&self) -> Option<Rc<dyn IDataTemplate>> {
876        if let Some(item_template) = self.item_template() {
877            return Some(item_template);
878        }
879
880        if self.display_member_item_template.borrow().is_none() {
881            if let Some(binding) = self.display_member_binding() {
882                let template: Rc<dyn IDataTemplate> = FuncDataTemplate::new(
883                    |_| true,
884                    move |_, _| {
885                        let text_block = TextBlock::new();
886                        text_block.bind_binding(TextBlock::text_property().as_property(), &*binding);
887                        Some(text_block.upcast())
888                    },
889                    false,
890                );
891                *self.display_member_item_template.borrow_mut() = Some(template);
892            }
893        }
894
895        self.display_member_item_template.borrow().clone()
896    }
897
898    fn update_pseudo_classes(&self) {
899        self.pseudo_classes().set(":empty", self.item_count() == 0);
900        self.pseudo_classes().set(":singleitem", self.item_count() == 1);
901    }
902
903    /// Whether an item is a visual: a control (see `Control::boxed`) or a
904    /// visual, held by any handle.
905    fn is_visual(item: &Option<BoxedValue>) -> bool {
906        match item {
907            Some(item) => {
908                let value: &dyn AnyValue = &**item;
909                value.is::<Ref<Control>>()
910                    || value.is::<Ref<Visual>>()
911                    || ferroui_base::data::core::ValueTypes::as_object(value).is_some_and(|o| o.is::<Visual>())
912            }
913            None => false,
914        }
915    }
916
917    /// Gets the next focusable, enabled and visible control of a container
918    /// in the specified direction.
919    pub fn get_next_control(
920        container: &dyn INavigableContainer,
921        mut direction: NavigationDirection,
922        from: Option<&Ref<InputElement>>,
923        wrap: bool,
924    ) -> Option<Ref<InputElement>> {
925        let mut from: Option<Ref<InputElement>> = from.cloned();
926        let mut current = from.clone();
927
928        loop {
929            let result = container.get_control(direction, current.as_ref(), wrap)?;
930
931            if result.focusable() && result.is_effectively_enabled() && result.is_effectively_visible() {
932                return Some(result);
933            }
934
935            current = Some(result.clone());
936
937            if current == from {
938                return None;
939            }
940
941            match direction {
942                // We did not find an enabled first item. Move downwards
943                // until we find one.
944                NavigationDirection::First => {
945                    direction = NavigationDirection::Down;
946                    from = Some(result);
947                }
948                // We did not find an enabled last item. Move upwards until
949                // we find one.
950                NavigationDirection::Last => {
951                    direction = NavigationDirection::Up;
952                    from = Some(result);
953                }
954                _ => {}
955            }
956        }
957    }
958
959    fn raise_child_index_changed(&self, e: &ChildIndexChangedEventArgs) {
960        if !self.child_index_changed.is_empty() {
961            for (_, handler) in self.child_index_changed.snapshot().iter() {
962                handler(e);
963            }
964        }
965    }
966}
967
968/// The child index provider of an items control.
969struct ItemsControlChildIndexProvider {
970    owner: WeakRef<ItemsControl>,
971}
972
973impl IChildIndexProvider for ItemsControlChildIndexProvider {
974    fn get_child_index(&self, child: &StyledElement) -> i32 {
975        let Some(owner) = self.owner.upgrade() else { return -1 };
976        match child.downcast_ref::<Control>() {
977            Some(container) => owner.index_from_container(&container.to_ref()),
978            None => -1,
979        }
980    }
981
982    fn try_get_total_count(&self) -> Option<i32> {
983        let owner = self.owner.upgrade()?;
984        Some(owner.items_view().count() as i32)
985    }
986
987    fn child_index_changed(&self, handler: Rc<dyn Fn(&ChildIndexChangedEventArgs)>) -> Rc<dyn IDisposable> {
988        let Some(owner) = self.owner.upgrade() else { return Disposable::empty() };
989        let token = owner.child_index_changed.add(handler);
990        let handlers = Rc::downgrade(&owner.child_index_changed);
991        Disposable::create(move || {
992            if let Some(handlers) = handlers.upgrade() {
993                handlers.remove(token);
994            }
995        })
996    }
997}