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::RouterContext;
8
9use crate::{
10    Route,
11    node::NodeElement,
12    state::DevtoolsChannel,
13};
14
15#[derive(Clone, PartialEq)]
16struct NodeTreeItem {
17    is_open: Option<bool>,
18    window_id: u64,
19    node_id: NodeId,
20}
21
22#[derive(PartialEq)]
23pub struct NodesTree {
24    pub selected_node_id: Option<NodeId>,
25    pub selected_window_id: Option<u64>,
26    pub on_selected: EventHandler<(u64, NodeId)>,
27    pub on_hover: EventHandler<(u64, Option<NodeId>)>,
28}
29
30impl NodesTree {
31    /// Collect all descendant node IDs starting from a given node
32    fn collect_descendants(window_nodes: &[NodeInfo], node_id: NodeId) -> Vec<NodeId> {
33        let mut result = Vec::new();
34        let mut stack = vec![node_id];
35
36        while let Some(current_id) = stack.pop() {
37            result.push(current_id);
38
39            // Find children of current node and push to stack
40            for node in window_nodes.iter() {
41                if node.parent_id == Some(current_id) {
42                    stack.push(node.node_id);
43                }
44            }
45        }
46
47        result
48    }
49}
50
51impl Component for NodesTree {
52    fn render(&self) -> impl IntoElement {
53        let mut radio = use_radio(DevtoolsChannel::UpdatedTree);
54
55        let items = {
56            let radio = radio.read();
57            radio
58                .nodes
59                .iter()
60                .flat_map(|(window_id, nodes)| {
61                    let mut allowed_nodes = HashSet::new();
62                    nodes
63                        .iter()
64                        .filter_map(|node| {
65                            let parent_is_open = node
66                                .parent_id
67                                .map(|node_id| {
68                                    allowed_nodes.contains(&node_id)
69                                        && radio.expanded_nodes.contains(&(*window_id, node_id))
70                                })
71                                .unwrap_or(false);
72                            let is_top_height = node.height == 1;
73                            if parent_is_open || is_top_height {
74                                allowed_nodes.insert(node.node_id);
75                                let is_open = (node.children_len != 0).then_some(
76                                    radio.expanded_nodes.contains(&(*window_id, node.node_id)),
77                                );
78                                Some(NodeTreeItem {
79                                    is_open,
80                                    node_id: node.node_id,
81                                    window_id: *window_id,
82                                })
83                            } else {
84                                None
85                            }
86                        })
87                        .collect::<Vec<_>>()
88                })
89                .collect::<Vec<_>>()
90        };
91
92        if items.is_empty() {
93            return rect()
94                .center()
95                .expanded()
96                .child("Waiting for an app to connect...")
97                .into_element();
98        }
99
100        let items_len = items.len() as i32;
101
102        VirtualScrollView::new_with_data(
103            (
104                self.selected_node_id,
105                self.selected_window_id,
106                self.on_selected.clone(),
107                self.on_hover.clone(),
108            ),
109            move |i, (selected_node_id, selected_window_id, on_selected, on_hover)| {
110                let NodeTreeItem {
111                    window_id,
112                    node_id,
113                    is_open,
114                } = items[i];
115                let on_selected = on_selected.clone();
116                let on_hover = on_hover.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::NodeInspectorStyle { .. } => {
157                                RouterContext::get()
158                                    .push(Route::NodeInspectorStyle { node_id, window_id });
159                            }
160                            Route::NodeInspectorTextStyle { .. } => {
161                                RouterContext::get()
162                                    .push(Route::NodeInspectorTextStyle { node_id, window_id });
163                            }
164                            Route::NodeInspectorLayout { .. } => {
165                                RouterContext::get()
166                                    .push(Route::NodeInspectorLayout { node_id, window_id });
167                            }
168                            _ => {
169                                RouterContext::get()
170                                    .push(Route::NodeInspectorStyle { node_id, window_id });
171                            }
172                        }
173                    }),
174                    on_hover: EventHandler::new(move |node_id| {
175                        on_hover.call((window_id, node_id));
176                    }),
177                    node_id,
178                    window_id,
179                }
180                .into()
181            },
182        )
183        .length(items_len)
184        .item_size(27.)
185        .into()
186    }
187}