freya_devtools_app/tabs/
tree.rs1use 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 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 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}