Skip to main content

ferroui_controls/
virtualizing_panel.rs

1use crate::generators::ItemContainerGenerator;
2use crate::items_source::ItemsChangedEventArgs;
3use crate::utils::CollectionUtils;
4use crate::{Control, ControlImpl, ItemsControl, ItemsSourceView, Panel, PanelImpl};
5use ferroui_base::input::{INavigableContainer, InputElement, InputElementImpl, NavigationDirection};
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::LayoutableImpl;
8use ferroui_base::{ferro_class, ferro_impl_classes, FerroObjectImpl, Ref, StyledElementImpl, VisualImpl, WeakRef};
9use std::cell::{Cell, RefCell};
10use std::rc::Rc;
11
12/// Base class for panels that can be used to virtualize items for an
13/// [`ItemsControl`].
14///
15/// Panels should implement the abstract members of this class to provide
16/// virtualization of items in an [`ItemsControl`]. Derived panels can
17/// manage scrolling by implementing the logical scrollable contract or by
18/// listening to the effective viewport changed event.
19///
20/// The methods on the [`ItemContainerGenerator`] should be used to create,
21/// prepare and clear containers for items.
22///
23/// This class is abstract: its abstract members panic unless overridden.
24#[repr(C)]
25pub struct VirtualizingPanel {
26    base: Panel,
27    items_control: RefCell<Option<WeakRef<ItemsControl>>>,
28    items_changed_token: Cell<u64>,
29}
30
31ferro_class! {
32    VirtualizingPanel: Panel, virtuals VirtualizingPanelImpl: PanelImpl {
33        /// Scrolls the specified item into view.
34        ///
35        /// Returns the element with the specified index, or `None` if the
36        /// element could not be brought into view.
37        fn scroll_into_view(this, index: i32) -> Option<Ref<Control>>;
38        /// Returns the container for the item at the specified index.
39        ///
40        /// Returns the container for the item at the specified index within
41        /// the item collection, if the item is realized; otherwise, `None`.
42        ///
43        /// Note for implementors: if the item at the specified index is an
44        /// item-is-self-container item that has previously been realized,
45        /// then the item should be returned even if it currently falls
46        /// outside the realized viewport.
47        fn container_from_index(this, index: i32) -> Option<Ref<Control>>;
48        /// Returns the index to the item that has the specified realized
49        /// container.
50        ///
51        /// Returns the index to the item that corresponds to the specified
52        /// realized container, or -1 if `container` is not found.
53        fn index_from_container(this, container: &Ref<Control>) -> i32;
54        /// Gets the currently realized containers.
55        fn get_realized_containers(this) -> Option<Vec<Ref<Control>>>;
56        /// Gets the next control in the specified direction.
57        ///
58        /// `from` is the control from which movement begins and `wrap`
59        /// tells whether to wrap around when the first or last item is
60        /// reached.
61        fn get_control_in_direction(
62            this,
63            direction: NavigationDirection,
64            from: Option<&Ref<InputElement>>,
65            wrap: bool
66        ) -> Option<Ref<InputElement>>;
67        /// Called when the [`ItemsControl`] that owns the panel changes.
68        fn on_items_control_changed(this, old_value: Option<&Ref<ItemsControl>>);
69        /// Called when the `items` collection of the owner [`ItemsControl`]
70        /// changes.
71        ///
72        /// This method is called a `collection changed` event is raised by
73        /// the items, or when the items property of the items control is
74        /// set to a new collection, in which case the action will be a
75        /// reset.
76        fn on_items_changed(this, items: &Rc<ItemsSourceView>, e: &ItemsChangedEventArgs<'_>);
77    }
78}
79
80ferro_impl_classes!(
81    VirtualizingPanel: FerroObjectImpl,
82    StyledElementImpl,
83    VisualImpl,
84    LayoutableImpl,
85    InteractiveImpl,
86    InputElementImpl,
87    ControlImpl
88);
89
90impl PanelImpl for VirtualizingPanel {
91    fn invalidate_measure_on_children_changed(_this: &Self) {
92        // Don't invalidate measure when children are added or removed: the
93        // panel is responsible for managing its children.
94    }
95}
96
97impl VirtualizingPanelImpl for VirtualizingPanel {
98    fn scroll_into_view(_this: &Self, _index: i32) -> Option<Ref<Control>> {
99        panic!("VirtualizingPanel is abstract.")
100    }
101
102    fn container_from_index(_this: &Self, _index: i32) -> Option<Ref<Control>> {
103        panic!("VirtualizingPanel is abstract.")
104    }
105
106    fn index_from_container(_this: &Self, _container: &Ref<Control>) -> i32 {
107        panic!("VirtualizingPanel is abstract.")
108    }
109
110    fn get_realized_containers(_this: &Self) -> Option<Vec<Ref<Control>>> {
111        panic!("VirtualizingPanel is abstract.")
112    }
113
114    fn get_control_in_direction(
115        _this: &Self,
116        _direction: NavigationDirection,
117        _from: Option<&Ref<InputElement>>,
118        _wrap: bool,
119    ) -> Option<Ref<InputElement>> {
120        panic!("VirtualizingPanel is abstract.")
121    }
122
123    fn on_items_control_changed(_this: &Self, _old_value: Option<&Ref<ItemsControl>>) {}
124
125    fn on_items_changed(_this: &Self, _items: &Rc<ItemsSourceView>, _e: &ItemsChangedEventArgs<'_>) {}
126}
127
128impl INavigableContainer for VirtualizingPanel {
129    fn get_control(
130        &self,
131        direction: NavigationDirection,
132        from: Option<&Ref<InputElement>>,
133        wrap: bool,
134    ) -> Option<Ref<InputElement>> {
135        self.get_control_in_direction(direction, from, wrap)
136    }
137}
138
139impl VirtualizingPanel {
140    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
141    pub fn construct() -> Self {
142        Self { base: Panel::construct(), items_control: RefCell::new(None), items_changed_token: Cell::new(0) }
143    }
144
145    /// Gets the [`ItemContainerGenerator`] for this [`VirtualizingPanel`].
146    pub fn item_container_generator(&self) -> Option<Rc<ItemContainerGenerator>> {
147        self.items_control().map(|items_control| items_control.item_container_generator())
148    }
149
150    /// Gets the items to display.
151    pub fn items(&self) -> Rc<ItemsSourceView> {
152        match self.items_control() {
153            Some(items_control) => items_control.items_view().clone(),
154            None => ItemsSourceView::empty(),
155        }
156    }
157
158    /// Gets the [`ItemsControl`] that the panel is displaying items for.
159    pub fn items_control(&self) -> Option<Ref<ItemsControl>> {
160        self.items_control.borrow().as_ref().and_then(WeakRef::upgrade)
161    }
162
163    fn set_items_control(&self, value: Option<Ref<ItemsControl>>) {
164        let old_value = self.items_control();
165        if old_value != value {
166            *self.items_control.borrow_mut() = value.as_ref().map(Ref::downgrade);
167            self.on_items_control_changed(old_value.as_ref());
168        }
169    }
170
171    /// Adds the specified [`Control`] to the children collection of a
172    /// [`VirtualizingPanel`] element.
173    pub fn add_internal_child(&self, control: &Ref<Control>) {
174        let items_control = self.ensure_items_control();
175        items_control.add_logical_child(control);
176        self.children().add(control);
177    }
178
179    /// Adds the specified [`Control`] to the children collection of a
180    /// [`VirtualizingPanel`] element at the specified index position.
181    pub fn insert_internal_child(&self, index: usize, control: &Ref<Control>) {
182        let items_control = self.ensure_items_control();
183        items_control.add_logical_child(control);
184        self.children().insert(index, control);
185    }
186
187    /// Removes a child element from the children collection.
188    pub fn remove_internal_child(&self, child: &Ref<Control>) {
189        let items_control = self.ensure_items_control();
190        items_control.remove_logical_child(child);
191        self.children().remove(child);
192    }
193
194    /// Removes child elements from the children collection.
195    pub fn remove_internal_child_range(&self, index: usize, count: usize) {
196        let items_control = self.ensure_items_control();
197
198        for i in index..count {
199            let c = self.children().get(i);
200            items_control.remove_logical_child(&c);
201        }
202
203        self.children().remove_range(index, count);
204    }
205
206    pub(crate) fn attach(&self, items_control: &Ref<ItemsControl>) {
207        if self.items_control().is_some() {
208            panic!("The VirtualizingPanel is already attached to an ItemsControl");
209        }
210
211        self.set_items_control(Some(items_control.clone()));
212
213        let weak = self.to_ref().downgrade();
214        let token = items_control.items_view().add_post_collection_changed(Rc::new(
215            move |e: &ItemsChangedEventArgs<'_>| {
216                if let Some(this) = weak.upgrade() {
217                    this.on_items_control_items_changed(e);
218                }
219            },
220        ));
221        self.items_changed_token.set(token);
222    }
223
224    pub(crate) fn detach(&self) {
225        let items_control = self.ensure_items_control();
226        items_control.items_view().remove_post_collection_changed(self.items_changed_token.get());
227        self.set_items_control(None);
228        self.children().clear();
229    }
230
231    pub(crate) fn refresh(&self) {
232        self.on_items_control_items_changed(&CollectionUtils::RESET_EVENT_ARGS);
233    }
234
235    fn ensure_items_control(&self) -> Ref<ItemsControl> {
236        match self.items_control() {
237            Some(items_control) => items_control,
238            None => panic!("The VirtualizingPanel does not belong to an ItemsControl."),
239        }
240    }
241
242    fn on_items_control_items_changed(&self, e: &ItemsChangedEventArgs<'_>) {
243        self.on_items_changed(&self.items(), e);
244    }
245}