freya_devtools_app/tabs/
tree.rs1use std::collections::HashSet;
2
3use freya::prelude::*;
4use freya_core::integration::NodeId;
5use freya_radio::prelude::use_radio;
6use freya_router::prelude::{
7 Navigator,
8 RouterContext,
9};
10
11use crate::{
12 Route,
13 node::NodeElement,
14 state::DevtoolsChannel,
15};
16
17#[derive(Clone, PartialEq)]
18struct NodeTreeItem {
19 is_open: Option<bool>,
20 window_id: u64,
21 node_id: NodeId,
22}
23
24#[derive(PartialEq)]
25pub struct NodesTree {
26 pub selected_node_id: Option<NodeId>,
27 pub selected_window_id: Option<u64>,
28 pub on_selected: EventHandler<(u64, NodeId)>,
29}
30
31impl Render for NodesTree {
32 fn render(&self) -> impl IntoElement {
33 let mut radio = use_radio(DevtoolsChannel::UpdatedTree);
34
35 let items = {
36 let radio = radio.read();
37 radio
38 .nodes
39 .iter()
40 .flat_map(|(window_id, nodes)| {
41 let mut allowed_nodes = HashSet::new();
42 nodes
43 .iter()
44 .filter_map(|node| {
45 let parent_is_open = node
46 .parent_id
47 .map(|node_id| {
48 allowed_nodes.contains(&node_id)
49 && radio.expanded_nodes.contains(&(*window_id, node_id))
50 })
51 .unwrap_or(false);
52 let is_top_height = node.height == 1;
53 if parent_is_open || is_top_height {
54 allowed_nodes.insert(node.node_id);
55 let is_open = (node.children_len != 0).then_some(
56 radio.expanded_nodes.contains(&(*window_id, node.node_id)),
57 );
58 Some(NodeTreeItem {
59 is_open,
60 node_id: node.node_id,
61 window_id: *window_id,
62 })
63 } else {
64 None
65 }
66 })
67 .collect::<Vec<_>>()
68 })
69 .collect::<Vec<_>>()
70 };
71
72 if items.is_empty() {
73 return rect()
74 .center()
75 .expanded()
76 .child("Waiting for an app to connect...")
77 .into_element();
78 }
79
80 let items_len = items.len() as i32;
81
82 VirtualScrollView::new_with_data(
83 (
84 self.selected_node_id,
85 self.selected_window_id,
86 self.on_selected.clone(),
87 ),
88 move |i, (selected_node_id, selected_window_id, on_selected)| {
89 let NodeTreeItem {
90 window_id,
91 node_id,
92 is_open,
93 } = items[i];
94 let on_selected = on_selected.clone();
95 NodeElement {
96 is_selected: Some(node_id) == *selected_node_id
97 && Some(window_id) == *selected_window_id,
98 is_open,
99 on_arrow: EventHandler::new(move |_| {
100 let mut radio = radio.write();
101 if radio.expanded_nodes.contains(&(window_id, node_id)) {
102 radio.expanded_nodes.remove(&(window_id, node_id));
103 } else {
104 radio.expanded_nodes.insert((window_id, node_id));
105 }
106 }),
107 on_selected: EventHandler::new(move |_| {
108 on_selected.call((window_id, node_id));
109 match RouterContext::get().current::<Route>() {
110 Route::NodeInspectorComputedLayout { .. } => {
111 Navigator::get().push(Route::NodeInspectorComputedLayout {
112 node_id,
113 window_id,
114 });
115 }
116 Route::NodeInspectorStyle { .. } => {
117 Navigator::get()
118 .push(Route::NodeInspectorStyle { node_id, window_id });
119 }
120 Route::NodeInspectorTextStyle { .. } => {
121 Navigator::get()
122 .push(Route::NodeInspectorTextStyle { node_id, window_id });
123 }
124 Route::NodeInspectorLayout { .. } => {
125 Navigator::get()
126 .push(Route::NodeInspectorLayout { node_id, window_id });
127 }
128 _ => {
129 Navigator::get()
130 .push(Route::NodeInspectorStyle { node_id, window_id });
131 }
132 }
133 }),
134 node_id,
135 window_id,
136 }
137 .into()
138 },
139 )
140 .length(items_len)
141 .item_size(27.)
142 .into()
143 }
144}