Skip to main content

ferroui_controls/generators/
item_container_generator.rs

1use crate::{Control, ItemsControl};
2use ferroui_base::{BoxedValue, Ref, WeakRef};
3use std::any::TypeId;
4use std::cell::Cell;
5
6/// A key that locates previously recycled containers of the right kind for
7/// an item.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum RecycleKey {
10    /// The default key, for an items control that supports a single
11    /// container type.
12    Default,
13    /// A key identifying containers by a Rust type.
14    Type(TypeId),
15    /// A key allocated with [`RecycleKey::new_unique`] or chosen by the
16    /// items control.
17    Id(u64),
18}
19
20impl RecycleKey {
21    /// Allocates a key that is different from every other key on the
22    /// current thread.
23    pub fn new_unique() -> Self {
24        ferroui_base::ferro_thread_local! {
25            static NEXT: Cell<u64> = const { Cell::new(1 << 63) };
26        }
27        RecycleKey::Id(NEXT.with(|next| next.replace(next.get() + 1)))
28    }
29
30    /// The key identifying containers by the type `T`.
31    pub fn of<T: 'static>() -> Self {
32        RecycleKey::Type(TypeId::of::<T>())
33    }
34}
35
36/// Generates containers for an [`ItemsControl`].
37///
38/// When creating a container for an item from a `VirtualizingPanel`, the
39/// following process should be followed:
40///
41/// - [`needs_container`](Self::needs_container) should first be called to
42///   determine whether the item needs a container. This method will return
43///   true if the item should be wrapped in a container control, or false if
44///   the item itself can be used as a container.
45/// - If `needs_container` returns true then the
46///   [`create_container`](Self::create_container) method should be called
47///   to create a new container, passing the recycle key returned from
48///   `needs_container`.
49/// - If the panel supports recycling and the recycle key is non-null then
50///   the recycle key should be recorded for the container (e.g. in an
51///   attached property or the realized container list).
52/// - [`prepare_item_container`](Self::prepare_item_container) method should
53///   be called for the container.
54/// - The container should then be added to the panel using
55///   `VirtualizingPanel::add_internal_child`.
56/// - Finally, [`item_container_prepared`](Self::item_container_prepared)
57///   should be called.
58///
59/// NOTE: If `needs_container` in the first step above returns false then
60/// the above steps should be carried out a single time: the first time the
61/// item is displayed. Otherwise the steps should be carried out each time a
62/// new container is realized for an item.
63///
64/// When unrealizing a container, the following process should be followed:
65///
66/// - If `needs_container` for the item returned false then the item cannot
67///   be unrealized or recycled.
68/// - Otherwise, [`clear_item_container`](Self::clear_item_container) should
69///   be called for the container.
70/// - If recycling is supported by the panel and the container then the
71///   container should be added to a recycle pool keyed on the recycle key
72///   returned from `needs_container`. It is assumed that recycled
73///   containers will not be removed from the panel but instead hidden from
74///   view using e.g. `container.set_is_visible(false)`.
75/// - If recycling is not supported then the container should be removed
76///   from the panel.
77///
78/// When recycling an unrealized container, the following process should be
79/// followed:
80///
81/// - `needs_container` should be called to determine whether the item needs
82///   a container, and if so, the recycle key.
83/// - A container should be taken from the recycle pool keyed on the
84///   returned recycle key.
85/// - The container should be made visible.
86/// - `prepare_item_container` method should be called for the container.
87/// - `item_container_prepared` should be called.
88///
89/// NOTE: Although this class is similar to that found in other XAML
90/// frameworks, here this class only concerns itself with generating and
91/// clearing item containers; it does not maintain a record of the currently
92/// realized containers, that responsibility is delegated to the items
93/// panel.
94pub struct ItemContainerGenerator {
95    owner: WeakRef<ItemsControl>,
96}
97
98impl ItemContainerGenerator {
99    pub(crate) fn new(owner: &ItemsControl) -> Self {
100        Self { owner: owner.to_ref().downgrade() }
101    }
102
103    #[inline]
104    fn owner(&self) -> Ref<ItemsControl> {
105        self.owner.upgrade().expect("the items control of the generator has been released")
106    }
107
108    /// Determines whether the specified item needs to be wrapped in a
109    /// container control.
110    ///
111    /// Returns true if the item needs a container; otherwise false if the
112    /// item can itself be used as a container. The second value is a key
113    /// that can be used to locate a previously recycled container of the
114    /// correct type, or `None` if the item cannot be recycled.
115    pub fn needs_container(&self, item: &Option<BoxedValue>, index: i32) -> (bool, Option<RecycleKey>) {
116        self.owner().needs_container_override(item, index)
117    }
118
119    /// Creates a new container control.
120    ///
121    /// Before calling this method, [`needs_container`](Self::needs_container)
122    /// should be called to determine whether the item itself should be used
123    /// as a container. After calling this method,
124    /// [`prepare_item_container`](Self::prepare_item_container) must be
125    /// called to prepare the container to display the specified item.
126    ///
127    /// If the panel supports recycling then the returned recycle key should
128    /// be stored alongside the container and when container becomes
129    /// eligible for recycling the container should be placed in a recycle
130    /// pool using this key. If the returned recycle key is `None` then the
131    /// container cannot be recycled.
132    pub fn create_container(
133        &self,
134        item: &Option<BoxedValue>,
135        index: i32,
136        recycle_key: Option<RecycleKey>,
137    ) -> Ref<Control> {
138        self.owner().create_container_for_item_override(item, index, recycle_key)
139    }
140
141    /// Prepares the specified element as the container for the
142    /// corresponding item.
143    ///
144    /// If [`needs_container`](Self::needs_container) is false for an item,
145    /// then this method must only be called a single time; otherwise this
146    /// method must be called after the container is created, and each
147    /// subsequent time the container is recycled to display a new item.
148    pub fn prepare_item_container(&self, container: &Ref<Control>, item: &Option<BoxedValue>, index: i32) {
149        self.owner().prepare_item_container(container, item, index)
150    }
151
152    /// Notifies the [`ItemsControl`] that a container has been fully
153    /// prepared to display an item.
154    ///
155    /// This method must be called when a container has been fully prepared
156    /// and added to the logical and visual trees, but may be called before
157    /// a layout pass has completed. It must be called regardless of the
158    /// result of [`needs_container`](Self::needs_container) but if that
159    /// method returned false then must be called only a single time.
160    pub fn item_container_prepared(&self, container: &Ref<Control>, item: &Option<BoxedValue>, index: i32) {
161        self.owner().item_container_prepared(container, item, index)
162    }
163
164    /// Called when the index for a container changes due to an insertion or
165    /// removal in the items collection.
166    pub fn item_container_index_changed(&self, container: &Ref<Control>, old_index: i32, new_index: i32) {
167        self.owner().item_container_index_changed(container, old_index, new_index)
168    }
169
170    /// Undoes the effects of the
171    /// [`prepare_item_container`](Self::prepare_item_container) method.
172    ///
173    /// This method must be called when a container is unrealized. The
174    /// container must have already have been removed from the virtualizing
175    /// panel's list of realized containers before this method is called.
176    /// This method must not be called if
177    /// [`needs_container`](Self::needs_container) returned false for the
178    /// item.
179    pub fn clear_item_container(&self, container: &Ref<Control>) {
180        self.owner().clear_item_container(container)
181    }
182}