freya_devtools_app/
node.rs

1use freya::prelude::*;
2use freya_core::integration::NodeId;
3
4use crate::hooks::use_node_info;
5
6#[derive(PartialEq)]
7pub struct NodeElement {
8    pub node_id: NodeId,
9    pub window_id: u64,
10    pub is_selected: bool,
11    pub is_open: Option<bool>,
12    pub on_selected: EventHandler<()>,
13    pub on_toggle: EventHandler<()>,
14    pub on_expand_all: EventHandler<()>,
15    pub on_collapse_all: EventHandler<()>,
16    pub on_hover: EventHandler<Option<NodeId>>,
17}
18
19impl Component for NodeElement {
20    fn render_key(&self) -> DiffKey
21    where
22        Self: ComponentKey,
23    {
24        DiffKey::from(&(self.node_id, self.window_id))
25    }
26
27    fn render(&self) -> impl IntoElement {
28        let Some(node) = use_node_info(self.node_id, self.window_id) else {
29            return rect().into_element();
30        };
31
32        let margin_left = ((node.height + 1) * 10) as f32 - 18.;
33        let id = self.node_id.0;
34
35        let role = node.state.accessibility.builder.role();
36
37        let on_select = {
38            let on_selected = self.on_selected.clone();
39            move |_| on_selected.call(())
40        };
41
42        let on_open = {
43            let handler = self.on_toggle.clone();
44            let is_open = self.is_open;
45            move |e: Event<PressEventData>| {
46                if is_open.is_some() {
47                    handler.call(());
48                    e.stop_propagation();
49                }
50            }
51        };
52
53        let on_hover_enter = {
54            let on_hover = self.on_hover.clone();
55            let node_id = self.node_id;
56            move |_| on_hover.call(Some(node_id))
57        };
58
59        let on_hover_leave = {
60            let on_hover = self.on_hover.clone();
61            move |_| on_hover.call(None)
62        };
63
64        let arrow_button = self.is_open.map(|is_open| {
65            let arrow_degrees = if is_open { 0. } else { 270. };
66            Button::new()
67                .corner_radius(99.)
68                .border_fill(Color::TRANSPARENT)
69                .padding(Gaps::new_all(6.))
70                .background(Color::TRANSPARENT)
71                .on_press(on_open)
72                .child(ArrowIcon::new().rotate(arrow_degrees).fill(Color::WHITE))
73        });
74
75        let on_secondary_press = {
76            let on_expand = self.on_toggle.clone();
77            let on_expand_all = self.on_expand_all.clone();
78            let on_collapse_all = self.on_collapse_all.clone();
79            let is_open = self.is_open;
80            move |_| {
81                let on_expand = on_expand.clone();
82                let on_expand_all = on_expand_all.clone();
83                let on_collapse_all = on_collapse_all.clone();
84                ContextMenu::open(
85                    Menu::new()
86                        .child(
87                            MenuItem::new()
88                                .on_press({
89                                    let on_expand = on_expand.clone();
90                                    move |_| {
91                                        on_expand.call(());
92                                    }
93                                })
94                                .child(if Some(true) == is_open {
95                                    "Collapse"
96                                } else {
97                                    "Expand"
98                                }),
99                        )
100                        .child(
101                            MenuItem::new()
102                                .on_press({
103                                    let on_expand_all = on_expand_all.clone();
104                                    move |_| {
105                                        on_expand_all.call(());
106                                    }
107                                })
108                                .child("Expand All"),
109                        )
110                        .child(
111                            MenuItem::new()
112                                .on_press({
113                                    let on_collapse_all = on_collapse_all.clone();
114                                    move |_| {
115                                        on_collapse_all.call(());
116                                    }
117                                })
118                                .child("Collapse All"),
119                        ),
120                );
121            }
122        };
123
124        let button = Button::new()
125            .corner_radius(99.)
126            .width(Size::fill())
127            .height(Size::px(27.))
128            .border_fill(Color::TRANSPARENT)
129            .background(if self.is_selected {
130                (40, 40, 40).into()
131            } else {
132                Color::TRANSPARENT
133            })
134            .hover_background(if self.is_selected {
135                (40, 40, 40).into()
136            } else {
137                Color::from((45, 45, 45))
138            })
139            .on_press(on_select)
140            .on_secondary_press(on_secondary_press)
141            .child(
142                rect()
143                    .offset_x(margin_left)
144                    .direction(Direction::Horizontal)
145                    .width(Size::fill())
146                    .cross_align(Alignment::center())
147                    .child(rect().width(Size::px(25.)).maybe_child(arrow_button))
148                    .child(
149                        paragraph()
150                            .max_lines(1)
151                            .font_size(14.)
152                            .text_overflow(TextOverflow::Ellipsis)
153                            .span(
154                                Span::new(if node.is_window {
155                                    "Window".to_string()
156                                } else if role == AccessibilityRole::GenericContainer {
157                                    "rect".to_string()
158                                } else {
159                                    format!("{role:?}")
160                                })
161                                .color(Color::WHITE),
162                            )
163                            .span(
164                                Span::new(if node.is_window {
165                                    format!(", id: {}", self.window_id)
166                                } else {
167                                    format!(", id: {}", id)
168                                })
169                                .color(Color::from_rgb(200, 200, 200)),
170                            ),
171                    ),
172            );
173
174        rect()
175            .on_pointer_enter(on_hover_enter)
176            .on_pointer_leave(on_hover_leave)
177            .child(button)
178            .into()
179    }
180}