Skip to main content

ferroui_diagnostics/diagnostics/view_models/
logical_tree_node.rs

1//! Port of `Diagnostics/ViewModels/LogicalTreeNode.cs`.
2
3use super::{TreeNode, TreeNodeCollection, TreeNodeCollectionImpl, TreeNodeKind};
4use crate::diagnostics::controls::TopLevelGroup;
5use crate::diagnostics::views::MainWindow;
6use ferroui_base::collections::{FerroList, FerroListExtensions};
7use ferroui_base::reactive::{CompositeDisposable, Disposable, IDisposable};
8use ferroui_base::{FerroObject, Nullable, Ref, StyledElement};
9use ferroui_controls::TopLevel;
10use std::cell::RefCell;
11use std::rc::{Rc, Weak};
12
13/// A node of the logical tree.
14///
15/// The original derives the node class from [`TreeNode`]; here a logical
16/// tree node is a [`TreeNode`] of the kind [`TreeNodeKind::Logical`], made
17/// by the functions of this type.
18pub struct LogicalTreeNode;
19
20impl LogicalTreeNode {
21    /// `LogicalTreeNode(FerroObject ferroObject, TreeNode? parent)`: the
22    /// node of an object of the logical tree, whose children are the logical
23    /// children of the element, or the top levels of a group of top levels.
24    pub fn new(ferro_object: Ref<FerroObject>, parent: Option<&Rc<TreeNode>>) -> Rc<TreeNode> {
25        let logical = ferro_object.cast::<StyledElement>();
26        let host = ferro_object.cast::<TopLevelGroup>();
27        TreeNode::new_node(ferro_object, parent, None, TreeNodeKind::Logical, move |this| {
28            if let Some(logical) = logical {
29                LogicalTreeNodeCollection::new(this, logical)
30            } else if let Some(host) = host {
31                TopLevelGroupHostLogical::new(this, host)
32            } else {
33                TreeNodeCollection::empty()
34            }
35        })
36    }
37
38    /// `Create(object control)`: the root node of the logical tree of
39    /// `control`, or no node when it is not an object of the framework.
40    pub fn create(control: impl Into<Nullable<FerroObject>>) -> Vec<Rc<TreeNode>> {
41        match control.into().0 {
42            Some(logical) => vec![LogicalTreeNode::new(logical, None)],
43            None => Vec::new(),
44        }
45    }
46}
47
48/// `LogicalTreeNode.LogicalTreeNodeCollection`: the nodes of the logical
49/// children of an element, following them.
50pub struct LogicalTreeNodeCollection {
51    control: Ref<StyledElement>,
52    subscription: RefCell<Option<Rc<dyn IDisposable>>>,
53}
54
55impl LogicalTreeNodeCollection {
56    pub fn new(owner: Weak<TreeNode>, control: Ref<StyledElement>) -> Rc<TreeNodeCollection> {
57        TreeNodeCollection::new(owner, Box::new(Self { control, subscription: RefCell::new(None) }))
58    }
59}
60
61impl TreeNodeCollectionImpl for LogicalTreeNodeCollection {
62    fn dispose(&self, this: &TreeNodeCollection) {
63        this.base_dispose();
64        let subscription = self.subscription.borrow().clone();
65        if let Some(subscription) = subscription {
66            subscription.dispose();
67        }
68    }
69
70    fn initialize(&self, this: &TreeNodeCollection, nodes: &FerroList<Rc<TreeNode>>) {
71        let owner = this.owner();
72        let owner = owner.as_ref().map(Rc::downgrade).unwrap_or_default();
73        let (added, removed, reset) = (nodes.clone(), nodes.clone(), nodes.clone());
74        let subscription = FerroListExtensions::for_each_item_indexed(
75            self.control.logical_children(),
76            move |i, item: &Ref<StyledElement>| {
77                let owner = owner.upgrade();
78                added.insert(i, LogicalTreeNode::new(item.clone().upcast(), owner.as_ref()));
79            },
80            move |i, _| removed.remove_at(i),
81            move || reset.clear(),
82            false,
83        );
84        *self.subscription.borrow_mut() = Some(subscription);
85    }
86}
87
88/// `LogicalTreeNode.TopLevelGroupHostLogical`: the nodes of the top levels
89/// of a group, except the window of the developer tools.
90pub struct TopLevelGroupHostLogical {
91    group: Ref<TopLevelGroup>,
92    subscriptions: CompositeDisposable,
93    /// Makes the node of a top level: a node of the logical tree, or of the combined tree (an
94    /// addition of the port).
95    factory: fn(Ref<FerroObject>, Option<&Rc<TreeNode>>) -> Rc<TreeNode>,
96}
97
98impl TopLevelGroupHostLogical {
99    pub fn new(owner: Weak<TreeNode>, host: Ref<TopLevelGroup>) -> Rc<TreeNodeCollection> {
100        Self::new_with(owner, host, LogicalTreeNode::new)
101    }
102
103    /// The nodes of the top levels of a group, each made by `factory`.
104    pub fn new_with(
105        owner: Weak<TreeNode>,
106        host: Ref<TopLevelGroup>,
107        factory: fn(Ref<FerroObject>, Option<&Rc<TreeNode>>) -> Rc<TreeNode>,
108    ) -> Rc<TreeNodeCollection> {
109        TreeNodeCollection::new(
110            owner,
111            Box::new(Self { group: host, subscriptions: CompositeDisposable::with_capacity(1), factory }),
112        )
113    }
114}
115
116impl TreeNodeCollectionImpl for TopLevelGroupHostLogical {
117    fn initialize(&self, this: &TreeNodeCollection, nodes: &FerroList<Rc<TreeNode>>) {
118        let owner = this.owner();
119        let items = self.group.items();
120        for window in items.iter() {
121            if window.is::<MainWindow>() {
122                continue;
123            }
124            nodes.add((self.factory)(window.clone().upcast(), owner.as_ref()));
125        }
126
127        let owner = owner.as_ref().map(Rc::downgrade).unwrap_or_default();
128        let factory = self.factory;
129
130        let group_on_added = {
131            let nodes = nodes.clone();
132            let owner = owner.clone();
133            move |e: &Ref<TopLevel>| {
134                if e.is::<MainWindow>() {
135                    return;
136                }
137
138                nodes.add(factory(e.clone().upcast(), owner.upgrade().as_ref()));
139            }
140        };
141        let group_on_removed = {
142            let nodes = nodes.clone();
143            move |e: &Ref<TopLevel>| {
144                if e.is::<MainWindow>() {
145                    return;
146                }
147
148                // As in the original, a removed top level adds a node.
149                nodes.add(factory(e.clone().upcast(), owner.upgrade().as_ref()));
150            }
151        };
152
153        let added = self.group.added(group_on_added);
154        let removed = self.group.removed(group_on_removed);
155
156        self.subscriptions.add(Disposable::create(move || {
157            added.dispose();
158            removed.dispose();
159        }));
160    }
161
162    fn dispose(&self, this: &TreeNodeCollection) {
163        self.subscriptions.dispose();
164        this.base_dispose();
165    }
166}