freya_devtools_app/tabs/
tree.rs

1use std::collections::HashSet;
2
3use freya::prelude::*;
4use freya_core::integration::NodeId;
5use freya_devtools::NodeInfo;
6use freya_radio::prelude::use_radio;
7use freya_router::prelude::{
8    Navigator,
9    RouterContext,
10};
11
12use crate::{
13    Route,
14    node::NodeElement,
15    state::DevtoolsChannel,
16};
17
18#[derive(Clone, PartialEq)]
19struct NodeTreeItem {
20    is_open: Option<bool>,
21    window_id: u64,
22    node_id: NodeId,
23}
24
25#[derive(PartialEq)]
26pub struct NodesTree {
27    pub selected_node_id: Option<NodeId>,
28    pub selected_window_id: Option<u64>,
29    pub on_selected: EventHandler<(u64, NodeId)>,
30}
31
32impl NodesTree {
33    /// Collect all descendant node IDs starting from a given node
34    fn collect_descendants(window_nodes: &[NodeInfo], node_id: NodeId) -> Vec<NodeId> {
35        let mut result = Vec::new();
36        let mut stack = vec![node_id];
37
38        while let Some(current_id) = stack.pop() {
39            result.push(current_id);
40
41            // Find children of current node and push to stack
42            for node in window_nodes.iter() {
43                if node.parent_id == Some(current_id) {
44                    stack.push(node.node_id);
45                }
46            }
47        }
48
49        result
50    }
51}
52
53impl Render for NodesTree {
54    fn render(&self) -> impl IntoElement {
55        let mut radio = use_radio(DevtoolsChannel::UpdatedTree);
56
57        let items = {
58            let radio = radio.read();
59            radio
60                .nodes
61                .iter()
62                .flat_map(|(window_id, nodes)| {
63                    let mut allowed_nodes = HashSet::new();
64                    nodes
65                        .iter()
66                        .filter_map(|node| {
67                            let parent_is_open = node
68                                .parent_id
69                                .map(|node_id| {
70                                    allowed_nodes.contains(&node_id)
71                                        && radio.expanded_nodes.contains(&(*window_id, node_id))
72                                })
73                                .unwrap_or(false);
74                            let is_top_height = node.height == 1;
75                            if parent_is_open || is_top_height {
76                                allowed_nodes.insert(node.node_id);
77                                let is_open = (node.children_len != 0).then_some(
78                                    radio.expanded_nodes.contains(&(*window_id, node.node_id)),
79                                );
80                                Some(NodeTreeItem {
81                                    is_open,
82                                    node_id: node.node_id,
83                                    window_id: *window_id,
84                                })
85                            } else {
86                                None
87                            }
88                        })
89                        .collect::<Vec<_>>()
90                })
91                .collect::<Vec<_>>()
92        };
93
94        if items.is_empty() {
95            return rect()
96                .center()
97                .expanded()
98                .child("Waiting for an app to connect...")
99                .into_element();
100        }
101
102        let items_len = items.len() as i32;
103
104        VirtualScrollView::new_with_data(
105            (
106                self.selected_node_id,
107                self.selected_window_id,
108                self.on_selected.clone(),
109            ),
110            move |i, (selected_node_id, selected_window_id, on_selected)| {
111                let NodeTreeItem {
112                    window_id,
113                    node_id,
114                    is_open,
115                } = items[i];
116                let on_selected = on_selected.clone();
117                NodeElement {
118                    is_selected: Some(node_id) == *selected_node_id
119                        && Some(window_id) == *selected_window_id,
120                    is_open,
121                    on_toggle: EventHandler::new(move |_| {
122                        let mut radio = radio.write();
123                        if radio.expanded_nodes.contains(&(window_id, node_id)) {
124                            radio.expanded_nodes.remove(&(window_id, node_id));
125                        } else {
126                            radio.expanded_nodes.insert((window_id, node_id));
127                        }
128                    }),
129                    on_expand_all: EventHandler::new(move |_| {
130                        let mut radio = radio.write();
131
132                        if let Some((_, window_nodes)) =
133                            radio.nodes.iter().find(|(id, _)| **id == window_id)
134                        {
135                            let descendants = NodesTree::collect_descendants(window_nodes, node_id);
136                            for nid in descendants {
137                                radio.expanded_nodes.insert((window_id, nid));
138                            }
139                        }
140                    }),
141                    on_collapse_all: EventHandler::new(move |_| {
142                        let mut radio = radio.write();
143
144                        if let Some((_, window_nodes)) =
145                            radio.nodes.iter().find(|(id, _)| **id == window_id)
146                        {
147                            let descendants = NodesTree::collect_descendants(window_nodes, node_id);
148                            for nid in descendants {
149                                radio.expanded_nodes.remove(&(window_id, nid));
150                            }
151                        }
152                    }),
153                    on_selected: EventHandler::new(move |_| {
154                        on_selected.call((window_id, node_id));
155                        match RouterContext::get().current::<Route>() {
156                            Route::NodeInspectorComputedLayout { .. } => {
157                                Navigator::get().push(Route::NodeInspectorComputedLayout {
158                                    node_id,
159                                    window_id,
160                                });
161                            }
162                            Route::NodeInspectorStyle { .. } => {
163                                Navigator::get()
164                                    .push(Route::NodeInspectorStyle { node_id, window_id });
165                            }
166                            Route::NodeInspectorTextStyle { .. } => {
167                                Navigator::get()
168                                    .push(Route::NodeInspectorTextStyle { node_id, window_id });
169                            }
170                            Route::NodeInspectorLayout { .. } => {
171                                Navigator::get()
172                                    .push(Route::NodeInspectorLayout { node_id, window_id });
173                            }
174                            _ => {
175                                Navigator::get()
176                                    .push(Route::NodeInspectorStyle { node_id, window_id });
177                            }
178                        }
179                    }),
180                    node_id,
181                    window_id,
182                }
183                .into()
184            },
185        )
186        .length(items_len)
187        .item_size(27.)
188        .into()
189    }
190}