Skip to main content

ferroui_diagnostics/diagnostics/view_models/
combined_tree_node.rs

1//! The combined tree of the developer tools: an addition of the port, with no counterpart upstream
2//! (Deviation, DEVIATIONS.md, Developer tools).
3//!
4//! The nodes are those of the logical tree, and below each element come first the parts of its
5//! template, shown greyed out: the visual children of the element that are not logical children
6//! (a visual is a logical child when its logical parent lists it among its logical children; the
7//! root of a template has the templated control as its logical parent but is not among its
8//! children), each with the visual children below it that are not logical children either. The
9//! logical children follow, in the order of the logical tree. A logical child that the template
10//! shows (the content of a button, in a content presenter of its template) is shown once, under
11//! its logical parent, not again under the part of the template it is in.
12
13use super::{TopLevelGroupHostLogical, TreeNode, TreeNodeCollection, TreeNodeCollectionImpl, TreeNodeKind};
14use crate::diagnostics::controls::TopLevelGroup;
15use ferroui_base::collections::{FerroList, FerroListExtensions};
16use ferroui_base::reactive::{CompositeDisposable, IDisposable};
17use ferroui_base::{FerroObject, Nullable, Ref, StyledElement, Visual};
18use std::cell::RefCell;
19use std::rc::{Rc, Weak};
20
21/// A node of the combined tree.
22pub struct CombinedTreeNode;
23
24impl CombinedTreeNode {
25    /// The node of an element of the logical tree: its children are the parts of its template,
26    /// then its logical children; or the node of the top levels of a group.
27    pub fn new_logical(ferro_object: Ref<FerroObject>, parent: Option<&Rc<TreeNode>>) -> Rc<TreeNode> {
28        let element = ferro_object.cast::<StyledElement>();
29        let host = ferro_object.cast::<TopLevelGroup>();
30        TreeNode::new_node(ferro_object, parent, None, TreeNodeKind::Combined { visual_only: false }, move |this| {
31            if let Some(element) = element {
32                CombinedChildren::new(this, element, true)
33            } else if let Some(host) = host {
34                TopLevelGroupHostLogical::new_with(this, host, CombinedTreeNode::new_logical)
35            } else {
36                TreeNodeCollection::empty()
37            }
38        })
39    }
40
41    /// The node of a part of a template: its children are its visual children that are not
42    /// logical children.
43    fn new_visual_only(element: Ref<StyledElement>, parent: Option<&Rc<TreeNode>>) -> Rc<TreeNode> {
44        let ferro_object: Ref<FerroObject> = element.clone().upcast();
45        TreeNode::new_node(ferro_object, parent, None, TreeNodeKind::Combined { visual_only: true }, move |this| {
46            CombinedChildren::new(this, element, false)
47        })
48    }
49
50    /// The root node of the combined tree of `control`, or no node when it is not an object of
51    /// the framework.
52    pub fn create(control: impl Into<Nullable<FerroObject>>) -> Vec<Rc<TreeNode>> {
53        match control.into().0 {
54            Some(root) => vec![CombinedTreeNode::new_logical(root, None)],
55            None => Vec::new(),
56        }
57    }
58
59    /// Whether `element` is a node of the logical tree: it and each of its logical ancestors are
60    /// among the logical children of their logical parents, up to the root (an element without a
61    /// logical parent). The root of a template has the templated control as its logical parent but
62    /// is not among its children, and neither it nor the elements logically below it (the child of
63    /// a border of the template) are nodes of the logical tree.
64    pub fn is_logical_child(element: &Ref<StyledElement>) -> bool {
65        let mut current = element.clone();
66        while let Some(parent) = current.get_logical_parent() {
67            if !parent.logical_children().to_vec().iter().any(|child| child.ptr_eq(&current)) {
68                return false;
69            }
70            current = parent;
71        }
72        true
73    }
74}
75
76/// The children of a node of the combined tree, following the visual children of its element
77/// (and its logical children, for a logical node).
78struct CombinedChildren {
79    element: Ref<StyledElement>,
80    /// Whether the node is a logical node, whose logical children follow the parts of its
81    /// template.
82    logical: bool,
83    subscriptions: CompositeDisposable,
84    /// The nodes shown now, kept so that the node of an element survives a change of its
85    /// siblings (with its expansion and its children).
86    current: Rc<RefCell<Vec<Rc<TreeNode>>>>,
87}
88
89impl CombinedChildren {
90    fn new(owner: Weak<TreeNode>, element: Ref<StyledElement>, logical: bool) -> Rc<TreeNodeCollection> {
91        TreeNodeCollection::new(
92            owner,
93            Box::new(Self {
94                element,
95                logical,
96                subscriptions: CompositeDisposable::with_capacity(2),
97                current: Rc::new(RefCell::new(Vec::new())),
98            }),
99        )
100    }
101}
102
103/// Makes the nodes of `element` again: the parts of its template, then its logical children
104/// when `logical`, reusing the nodes it already has for the same elements.
105fn rebuild(
106    element: &Ref<StyledElement>,
107    logical: bool,
108    owner: &Weak<TreeNode>,
109    current: &RefCell<Vec<Rc<TreeNode>>>,
110    nodes: &FerroList<Rc<TreeNode>>,
111) {
112    let owner = owner.upgrade();
113    let mut wanted: Vec<(Ref<StyledElement>, bool)> = Vec::new();
114    if let Some(visual) = element.cast::<Visual>() {
115        for child in visual.visual_children().to_vec() {
116            if let Some(child) = child.cast::<StyledElement>() {
117                if !CombinedTreeNode::is_logical_child(&child) {
118                    wanted.push((child, true));
119                }
120            }
121        }
122    }
123    if logical {
124        for child in element.logical_children().to_vec() {
125            wanted.push((child, false));
126        }
127    }
128
129    let previous = current.borrow().clone();
130    let next: Vec<Rc<TreeNode>> = wanted
131        .into_iter()
132        .map(|(child, visual_only)| {
133            let kind = TreeNodeKind::Combined { visual_only };
134            previous
135                .iter()
136                .find(|node| node.kind() == kind && node.visual().ptr_eq(&child))
137                .cloned()
138                .unwrap_or_else(|| {
139                    if visual_only {
140                        CombinedTreeNode::new_visual_only(child, owner.as_ref())
141                    } else {
142                        CombinedTreeNode::new_logical(child.upcast(), owner.as_ref())
143                    }
144                })
145        })
146        .collect();
147
148    let same = previous.len() == next.len() && previous.iter().zip(&next).all(|(a, b)| Rc::ptr_eq(a, b));
149    if same {
150        return;
151    }
152    for node in &previous {
153        if !next.iter().any(|kept| Rc::ptr_eq(kept, node)) {
154            node.dispose();
155        }
156    }
157    nodes.clear();
158    for node in &next {
159        nodes.add(node.clone());
160    }
161    *current.borrow_mut() = next;
162}
163
164impl TreeNodeCollectionImpl for CombinedChildren {
165    fn dispose(&self, this: &TreeNodeCollection) {
166        self.subscriptions.dispose();
167        this.base_dispose();
168    }
169
170    fn initialize(&self, this: &TreeNodeCollection, nodes: &FerroList<Rc<TreeNode>>) {
171        let owner = this.owner();
172        let owner = owner.as_ref().map(Rc::downgrade).unwrap_or_default();
173
174        rebuild(&self.element, self.logical, &owner, &self.current, nodes);
175
176        // A change of the visual or the logical children makes the nodes again.
177        let refresh = {
178            let element = self.element.clone();
179            let logical = self.logical;
180            let current = self.current.clone();
181            let nodes = nodes.clone();
182            Rc::new(move || rebuild(&element, logical, &owner, &current, &nodes))
183        };
184        if let Some(visual) = self.element.cast::<Visual>() {
185            let (added, removed, reset) = (refresh.clone(), refresh.clone(), refresh.clone());
186            self.subscriptions.add(FerroListExtensions::for_each_item_indexed(
187                visual.visual_children(),
188                move |_, _: &Ref<Visual>| added(),
189                move |_, _| removed(),
190                move || reset(),
191                false,
192            ));
193        }
194        if self.logical {
195            let (added, removed, reset) = (refresh.clone(), refresh.clone(), refresh);
196            self.subscriptions.add(FerroListExtensions::for_each_item_indexed(
197                self.element.logical_children(),
198                move |_, _: &Ref<StyledElement>| added(),
199                move |_, _| removed(),
200                move || reset(),
201                false,
202            ));
203        }
204    }
205}
206
207/// The subscriptions are disposed with the collection.
208impl Drop for CombinedChildren {
209    fn drop(&mut self) {
210        self.subscriptions.dispose();
211    }
212}