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