Skip to main content

ferroui_controls/
definition_list.rs

1use crate::{DefinitionBase, Grid};
2use ferroui_base::collections::{FerroList, NotifyCollectionChangedEventArgs, ResetBehavior};
3use ferroui_base::{ObjectType, Ref, Upcast, WeakRef};
4use std::cell::{Cell, RefCell};
5use std::ops::Deref;
6use std::rc::Rc;
7
8/// The state shared between a [`DefinitionList`] and its collection changed
9/// handler.
10struct DefinitionListData<T: ObjectType + Upcast<DefinitionBase>> {
11    list: FerroList<Ref<T>>,
12    is_dirty: Cell<bool>,
13    parent: RefCell<Option<WeakRef<Grid>>>,
14}
15
16/// Base class of the collections of column and row definitions.
17///
18/// When parsed from a string, the items are separated by "," or " ".
19pub struct DefinitionList<T: ObjectType + Upcast<DefinitionBase>> {
20    data: Rc<DefinitionListData<T>>,
21}
22
23impl<T: ObjectType + Upcast<DefinitionBase>> Clone for DefinitionList<T> {
24    fn clone(&self) -> Self {
25        Self { data: self.data.clone() }
26    }
27}
28
29/// Handles compare by identity.
30impl<T: ObjectType + Upcast<DefinitionBase>> PartialEq for DefinitionList<T> {
31    fn eq(&self, other: &Self) -> bool {
32        Rc::ptr_eq(&self.data, &other.data)
33    }
34}
35
36impl<T: ObjectType + Upcast<DefinitionBase>> Default for DefinitionList<T> {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl<T: ObjectType + Upcast<DefinitionBase>> DefinitionList<T> {
43    /// Creates an empty collection.
44    pub fn new() -> Self {
45        let data = Rc::new(DefinitionListData {
46            list: FerroList::new(),
47            is_dirty: Cell::new(true),
48            parent: RefCell::new(None),
49        });
50        data.list.set_reset_behavior(ResetBehavior::Remove);
51        let weak = Rc::downgrade(&data);
52        data.list
53            .add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Ref<T>>| {
54                if let Some(data) = weak.upgrade() {
55                    data.on_collection_changed(e);
56                }
57            }));
58        Self { data }
59    }
60
61    /// Whether the collection changed since the grid last validated its
62    /// structure.
63    #[inline]
64    pub(crate) fn is_dirty(&self) -> bool {
65        self.data.is_dirty.get()
66    }
67
68    #[inline]
69    pub(crate) fn set_is_dirty(&self, value: bool) {
70        self.data.is_dirty.set(value)
71    }
72
73    /// The grid that owns the collection.
74    #[allow(dead_code)]
75    pub(crate) fn parent(&self) -> Option<Ref<Grid>> {
76        self.data.parent()
77    }
78
79    pub(crate) fn set_parent(&self, value: Option<&Ref<Grid>>) {
80        self.data.set_parent(value)
81    }
82}
83
84impl<T: ObjectType + Upcast<DefinitionBase>> Deref for DefinitionList<T> {
85    type Target = FerroList<Ref<T>>;
86
87    #[inline]
88    fn deref(&self) -> &Self::Target {
89        &self.data.list
90    }
91}
92
93impl<T: ObjectType + Upcast<DefinitionBase>> DefinitionListData<T> {
94    fn parent(&self) -> Option<Ref<Grid>> {
95        self.parent.borrow().as_ref().and_then(WeakRef::upgrade)
96    }
97
98    fn set_parent(&self, value: Option<&Ref<Grid>>) {
99        if self.parent().as_ref() == value {
100            return;
101        }
102
103        *self.parent.borrow_mut() = value.map(Ref::downgrade);
104
105        // Definitions already present when the grid claims the collection
106        // never pass through the collection changed handler, so they have to
107        // change trees here.
108        for (idx, definition) in self.list.snapshot().iter().enumerate() {
109            let definition: &DefinitionBase = (**definition).upcast();
110            Self::set_definition_parent(definition, value);
111            definition.set_index(idx as i32);
112        }
113    }
114
115    /// Moves a definition from its current parent tree to `parent`. Every
116    /// route that changes a definition's owner goes through here.
117    ///
118    /// Ownership is more than the parent pointer: entering a tree also
119    /// establishes the property inheritance link a definition needs to see
120    /// its shared size scope, and leaving one releases that link and its
121    /// shared size registration.
122    fn set_definition_parent(definition: &DefinitionBase, parent: Option<&Ref<Grid>>) {
123        let current = definition.parent();
124        if current.as_ref() == parent {
125            return;
126        }
127
128        if current.is_some() {
129            definition.on_exit_parent_tree();
130        }
131
132        definition.set_parent(parent);
133
134        if parent.is_some() {
135            definition.on_enter_parent_tree();
136        }
137    }
138
139    fn on_collection_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Ref<T>>) {
140        for (idx, definition) in self.list.snapshot().iter().enumerate() {
141            let definition: &DefinitionBase = (**definition).upcast();
142            definition.set_index(idx as i32);
143        }
144
145        self.update_definition_parent(e.new_items, false);
146        self.update_definition_parent(e.old_items, true);
147
148        self.is_dirty.set(true);
149    }
150
151    fn update_definition_parent(&self, items: &[Ref<T>], was_removed: bool) {
152        if items.is_empty() {
153            return;
154        }
155
156        let parent = if was_removed { None } else { self.parent() };
157
158        for item in items {
159            Self::set_definition_parent((**item).upcast(), parent.as_ref());
160        }
161    }
162}