freya_devtools_app/tabs/
tree.rs

1use 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}