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 pub on_hover: EventHandler<(u64, Option<NodeId>)>,
31}
32
33impl NodesTree {
34 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 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}