freya_devtools_app/
node.rs1use 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}