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::{
7 Radio,
8 use_radio,
9};
10use freya_router::prelude::RouterContext;
11
12use crate::{
13 Route,
14 node::NodeElement,
15 state::{
16 DevtoolsChannel,
17 DevtoolsState,
18 },
19};
20
21#[derive(Clone, PartialEq)]
22struct NodeTreeItem {
23 is_open: Option<bool>,
24 window_id: u64,
25 node_id: NodeId,
26}
27
28#[derive(PartialEq)]
29pub struct NodesTree {
30 pub selected_node_id: Option<NodeId>,
31 pub selected_window_id: Option<u64>,
32 pub on_selected: EventHandler<(u64, NodeId)>,
33 pub on_hover: EventHandler<(u64, Option<NodeId>)>,
34}
35
36impl NodesTree {
37 fn collect_descendants(window_nodes: &[NodeInfo], node_id: NodeId) -> Vec<NodeId> {
39 let mut result = Vec::new();
40 let mut stack = vec![node_id];
41
42 while let Some(current_id) = stack.pop() {
43 result.push(current_id);
44
45 for node in window_nodes.iter() {
47 if node.parent_id == Some(current_id) {
48 stack.push(node.node_id);
49 }
50 }
51 }
52
53 result
54 }
55
56 fn set_subtree_expanded(
58 mut radio: Radio<DevtoolsState, DevtoolsChannel>,
59 window_id: u64,
60 node_id: NodeId,
61 expanded: bool,
62 ) {
63 let mut state = radio.write();
64 let Some(window_nodes) = state.nodes.get(&window_id) else {
65 return;
66 };
67 for descendant in Self::collect_descendants(window_nodes, node_id) {
68 if expanded {
69 state.expanded_nodes.insert((window_id, descendant));
70 } else {
71 state.expanded_nodes.remove(&(window_id, descendant));
72 }
73 }
74 }
75}
76
77impl Component for NodesTree {
78 fn render(&self) -> impl IntoElement {
79 let mut radio = use_radio(DevtoolsChannel::UpdatedTree);
80
81 let items = {
82 let radio = radio.read();
83 radio
84 .nodes
85 .iter()
86 .flat_map(|(window_id, nodes)| {
87 let mut allowed_nodes = HashSet::new();
88 nodes
89 .iter()
90 .filter_map(|node| {
91 let parent_is_open = node
92 .parent_id
93 .map(|node_id| {
94 allowed_nodes.contains(&node_id)
95 && radio.expanded_nodes.contains(&(*window_id, node_id))
96 })
97 .unwrap_or(false);
98 let is_top_height = node.height == 1;
99 if parent_is_open || is_top_height {
100 allowed_nodes.insert(node.node_id);
101 let is_open = (node.children_len != 0).then_some(
102 radio.expanded_nodes.contains(&(*window_id, node.node_id)),
103 );
104 Some(NodeTreeItem {
105 is_open,
106 node_id: node.node_id,
107 window_id: *window_id,
108 })
109 } else {
110 None
111 }
112 })
113 .collect::<Vec<_>>()
114 })
115 .collect::<Vec<_>>()
116 };
117
118 if items.is_empty() {
119 return rect()
120 .center()
121 .expanded()
122 .child("Waiting for an app to connect...")
123 .into_element();
124 }
125
126 let items_len = items.len();
127
128 VirtualScrollView::new_with_data(
129 (
130 self.selected_node_id,
131 self.selected_window_id,
132 self.on_selected.clone(),
133 self.on_hover.clone(),
134 ),
135 move |i, (selected_node_id, selected_window_id, on_selected, on_hover)| {
136 let NodeTreeItem {
137 window_id,
138 node_id,
139 is_open,
140 } = items[i];
141 let on_selected = on_selected.clone();
142 let on_hover = on_hover.clone();
143 NodeElement {
144 is_selected: Some(node_id) == *selected_node_id
145 && Some(window_id) == *selected_window_id,
146 is_open,
147 on_toggle: EventHandler::new(move |_| {
148 let mut radio = radio.write();
149 if radio.expanded_nodes.contains(&(window_id, node_id)) {
150 radio.expanded_nodes.remove(&(window_id, node_id));
151 } else {
152 radio.expanded_nodes.insert((window_id, node_id));
153 }
154 }),
155 on_expand_all: EventHandler::new(move |_| {
156 NodesTree::set_subtree_expanded(radio, window_id, node_id, true);
157 }),
158 on_collapse_all: EventHandler::new(move |_| {
159 NodesTree::set_subtree_expanded(radio, window_id, node_id, false);
160 }),
161 on_selected: EventHandler::new(move |_| {
162 on_selected.call((window_id, node_id));
163 let route = match RouterContext::get().current::<Route>() {
164 Route::NodeInspectorTextStyle { .. } => {
165 Route::NodeInspectorTextStyle { node_id, window_id }
166 }
167 Route::NodeInspectorLayout { .. } => {
168 Route::NodeInspectorLayout { node_id, window_id }
169 }
170 _ => Route::NodeInspectorStyle { node_id, window_id },
171 };
172 let _ = RouterContext::get().push(route);
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}