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::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 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 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}