ferroui_diagnostics/diagnostics/view_models/
combined_tree_node.rs1use 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
21pub struct CombinedTreeNode;
23
24impl CombinedTreeNode {
25 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 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 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 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(¤t)) {
68 return false;
69 }
70 current = parent;
71 }
72 true
73 }
74}
75
76struct CombinedChildren {
79 element: Ref<StyledElement>,
80 logical: bool,
83 subscriptions: CompositeDisposable,
84 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
103fn 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 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, ¤t, &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
207impl Drop for CombinedChildren {
209 fn drop(&mut self) {
210 self.subscriptions.dispose();
211 }
212}