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}