Skip to main content

ferroui_diagnostics/diagnostics/view_models/
tree_node_collection.rs

1//! Port of `Diagnostics/ViewModels/TreeNodeCollection.cs`.
2
3use super::TreeNode;
4use ferroui_base::collections::{CollectionChangedHandler, FerroList, IFerroReadOnlyList};
5use ferroui_base::data::model::{CollectionChange, Event, INotifyCollectionChanged, INotifyPropertyChanged};
6use ferroui_base::{ferro_markup_type, BoxedValue};
7use ferroui_controls::{IItemsList, ItemsChangedHandler};
8use ferroui_treedatagrid_core::model_collection::{ModelCollection, ModelsChangedHandler};
9use std::any::Any;
10use std::cell::OnceCell;
11use std::rc::{Rc, Weak};
12
13/// The members a kind of [`TreeNodeCollection`] overrides: the abstract
14/// `Initialize` and the virtual `Dispose` of the original.
15pub trait TreeNodeCollectionImpl {
16    /// `protected abstract void Initialize(FerroList<TreeNode> nodes)`:
17    /// fills `nodes` and keeps them up to date. Called once, when the
18    /// collection is first used.
19    fn initialize(&self, this: &TreeNodeCollection, nodes: &FerroList<Rc<TreeNode>>);
20
21    /// `public virtual void Dispose()`. The base implementation disposes the
22    /// nodes ([`TreeNodeCollection::base_dispose`]); an override calls it
23    /// where the original calls `base.Dispose()`.
24    fn dispose(&self, this: &TreeNodeCollection) {
25        this.base_dispose();
26    }
27}
28
29/// The children of a [`TreeNode`]: a read-only list of nodes that is filled
30/// the first time it is used (its count, an item, an enumeration or a
31/// subscription to its changes), and then follows the tree it shows.
32///
33/// The kinds of the original (the collections of the logical and visual
34/// trees, of a group of top levels, of an application, and the empty one)
35/// derive from it; here they implement [`TreeNodeCollectionImpl`], which the
36/// collection holds.
37pub struct TreeNodeCollection {
38    this: Weak<TreeNodeCollection>,
39    owner: Weak<TreeNode>,
40    inner: OnceCell<FerroList<Rc<TreeNode>>>,
41    kind: Box<dyn TreeNodeCollectionImpl>,
42}
43
44/// `EmptyTreeNodeCollection`: the collection of a node without children.
45struct EmptyTreeNodeCollection;
46
47impl TreeNodeCollectionImpl for EmptyTreeNodeCollection {
48    fn initialize(&self, _this: &TreeNodeCollection, _nodes: &FerroList<Rc<TreeNode>>) {}
49}
50
51ferroui_base::ferro_thread_local! {
52    static EMPTY: Rc<TreeNodeCollection> = TreeNodeCollection::new(Weak::new(), Box::new(EmptyTreeNodeCollection));
53}
54
55impl PartialEq for TreeNodeCollection {
56    fn eq(&self, other: &Self) -> bool {
57        std::ptr::eq(self, other)
58    }
59}
60
61impl TreeNodeCollection {
62    /// `TreeNodeCollection(TreeNode owner)`: a collection of the children of
63    /// `owner`, of the kind `kind`.
64    pub fn new(owner: Weak<TreeNode>, kind: Box<dyn TreeNodeCollectionImpl>) -> Rc<Self> {
65        Rc::new_cyclic(|this| Self { this: this.clone(), owner, inner: OnceCell::new(), kind })
66    }
67
68    /// The shared handle of the collection (a collection is always shared).
69    pub(crate) fn to_rc(&self) -> Rc<TreeNodeCollection> {
70        self.this.upgrade().expect("the collection is alive while it is used")
71    }
72
73    /// `static readonly internal TreeNodeCollection Empty`: the collection of
74    /// the nodes without children (one per thread).
75    pub fn empty() -> Rc<TreeNodeCollection> {
76        EMPTY.with(Rc::clone)
77    }
78
79    /// `this[int index]`: the node at `index`.
80    ///
81    /// # Panics
82    /// Panics when `index` is out of range.
83    pub fn get(&self, index: usize) -> Rc<TreeNode> {
84        self.ensure_initialized().get(index)
85    }
86
87    /// `Count`: the number of nodes.
88    pub fn count(&self) -> usize {
89        self.ensure_initialized().count()
90    }
91
92    /// `Owner`: the node whose children the collection holds; `None` for the
93    /// empty collection, and once the node is gone.
94    pub fn owner(&self) -> Option<Rc<TreeNode>> {
95        self.owner.upgrade()
96    }
97
98    /// `Dispose()`: the override of the kind of the collection.
99    pub fn dispose(&self) {
100        self.kind.dispose(self);
101    }
102
103    /// The base implementation of `Dispose()`: disposes the nodes, if the
104    /// collection was ever filled.
105    pub fn base_dispose(&self) {
106        if let Some(inner) = self.inner.get() {
107            for node in inner.snapshot().iter() {
108                node.dispose();
109            }
110        }
111    }
112
113    /// Whether the collection was filled: whether its nodes exist. A collection is filled the first
114    /// time it is used (its count, an item, an enumeration or a subscription to its changes); the
115    /// tree pages ask for the children of a node when its row is shown or expanded.
116    pub fn is_initialized(&self) -> bool {
117        self.inner.get().is_some()
118    }
119
120    /// `GetEnumerator()`: the nodes, as they are now.
121    pub fn to_vec(&self) -> Vec<Rc<TreeNode>> {
122        self.ensure_initialized().to_vec()
123    }
124
125    /// `IList.Contains(object)`.
126    pub fn contains(&self, node: &Rc<TreeNode>) -> bool {
127        self.ensure_initialized().contains(node)
128    }
129
130    /// `IList.IndexOf(object)`: the index of `node`, or `-1`.
131    pub fn index_of(&self, node: &Rc<TreeNode>) -> i32 {
132        self.ensure_initialized().index_of(node).map_or(-1, |index| index as i32)
133    }
134
135    /// The list of the nodes, created and filled by the kind of the
136    /// collection the first time it is needed. As in the original, the list
137    /// is stored before it is filled: what the filling triggers sees it.
138    fn ensure_initialized(&self) -> &FerroList<Rc<TreeNode>> {
139        if self.inner.get().is_none() {
140            let nodes = FerroList::new();
141            let _ = self.inner.set(nodes.clone());
142            self.kind.initialize(self, &nodes);
143        }
144        self.inner.get().expect("the nodes were just created")
145    }
146}
147
148impl INotifyCollectionChanged for TreeNodeCollection {
149    /// `CollectionChanged`: subscribing fills the collection.
150    fn collection_changed(&self) -> &Event<CollectionChange> {
151        self.ensure_initialized().collection_changed()
152    }
153}
154
155impl INotifyPropertyChanged for TreeNodeCollection {
156    /// `PropertyChanged`: subscribing fills the collection.
157    fn property_changed(&self) -> &Event<str> {
158        self.ensure_initialized().property_changed()
159    }
160}
161
162impl IFerroReadOnlyList<Rc<TreeNode>> for TreeNodeCollection {
163    fn count(&self) -> usize {
164        TreeNodeCollection::count(self)
165    }
166
167    fn get(&self, index: usize) -> Rc<TreeNode> {
168        TreeNodeCollection::get(self, index)
169    }
170
171    fn snapshot(&self) -> Rc<Vec<Rc<TreeNode>>> {
172        self.ensure_initialized().snapshot()
173    }
174
175    fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<Rc<TreeNode>>>) -> u64 {
176        self.ensure_initialized().add_collection_changed(handler)
177    }
178
179    fn remove_collection_changed(&self, token: u64) -> bool {
180        self.ensure_initialized().remove_collection_changed(token)
181    }
182}
183
184/// The collection as the items of an items control (the untyped `IList` of
185/// the original, which the tree view reads).
186impl IItemsList for TreeNodeCollection {
187    fn count(&self) -> usize {
188        TreeNodeCollection::count(self)
189    }
190
191    fn get_at(&self, index: usize) -> Option<BoxedValue> {
192        <FerroList<Rc<TreeNode>> as IItemsList>::get_at(self.ensure_initialized(), index)
193    }
194
195    fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
196        <FerroList<Rc<TreeNode>> as IItemsList>::index_of(self.ensure_initialized(), item)
197    }
198
199    fn is_notifying(&self) -> bool {
200        true
201    }
202
203    fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
204        <FerroList<Rc<TreeNode>> as IItemsList>::add_collection_changed(self.ensure_initialized(), handler)
205    }
206
207    fn remove_collection_changed(&self, token: u64) {
208        <FerroList<Rc<TreeNode>> as IItemsList>::remove_collection_changed(self.ensure_initialized(), token);
209    }
210
211    fn as_any(&self) -> &dyn Any {
212        self
213    }
214}
215
216// Deviation (DEVIATIONS.md, Developer tools): the tree pages show the nodes in a TreeDataGrid, not
217// in a tree view; the collection is the collection of the child models of a node for the
218// hierarchical source of a page (`TreePageViewModel::nodes_source`), which subscribes to its
219// changes.
220impl ModelCollection<TreeNode> for TreeNodeCollection {
221    fn count(&self) -> usize {
222        TreeNodeCollection::count(self)
223    }
224
225    fn get(&self, index: usize) -> Rc<TreeNode> {
226        TreeNodeCollection::get(self, index)
227    }
228
229    fn index_of(&self, model: &Rc<TreeNode>) -> Option<usize> {
230        self.ensure_initialized().index_of(model)
231    }
232
233    fn add_collection_changed(&self, handler: Rc<ModelsChangedHandler<TreeNode>>) -> Option<u64> {
234        Some(self.ensure_initialized().add_collection_changed(handler))
235    }
236
237    fn remove_collection_changed(&self, token: u64) {
238        self.ensure_initialized().remove_collection_changed(token);
239    }
240
241    fn is_notifying(&self) -> bool {
242        true
243    }
244
245    fn to_vec(&self) -> Vec<Rc<TreeNode>> {
246        TreeNodeCollection::to_vec(self)
247    }
248}
249
250ferro_markup_type!(class TreeNodeCollection {
251    this: Rc<TreeNodeCollection>,
252    handles: [TreeNodeCollection, Rc<TreeNodeCollection>, Option<Rc<TreeNodeCollection>>],
253    properties: [
254        Count: i32 { get: |this: &Rc<TreeNodeCollection>| this.count() as i32 },
255    ],
256});