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