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 }
70 })
71 .child(if Some(true) == is_open {
72 "Collapse"
73 } else {
74 "Expand"
75 }),
76 )
77 .child(
78 MenuItem::new()
79 .on_press({
80 let on_expand_all = on_expand_all.clone();
81 move |_| {
82 on_expand_all.call(());
83 }
84 })
85 .child("Expand All"),
86 )
87 .child(
88 MenuItem::new()
89 .on_press({
90 let on_collapse_all = on_collapse_all.clone();
91 move |_| {
92 on_collapse_all.call(());
93 }
94 })
95 .child("Collapse All"),
96 ),
97 );
98 }
99 };
100
101 let arrow_button = self.is_open.map(|is_open| {
102 let arrow_degrees = if is_open { 0. } else { 270. };
103 Button::new()
104 .corner_radius(99.)
105 .border_fill(Color::TRANSPARENT)
106 .padding(Gaps::new_all(6.))
107 .background(Color::TRANSPARENT)
108 .on_press(on_open)
109 .child(ArrowIcon::new().rotate(arrow_degrees).fill(Color::WHITE))
110 });
111
112 Button::new()
113 .corner_radius(99.)
114 .width(Size::fill())
115 .height(Size::px(27.))
116 .border_fill(Color::TRANSPARENT)
117 .background(if self.is_selected {
118 (40, 40, 40).into()
119 } else {
120 Color::TRANSPARENT
121 })
122 .hover_background(if self.is_selected {
123 (40, 40, 40).into()
124 } else {
125 Color::from((45, 45, 45))
126 })
127 .on_press(on_select)
128 .on_secondary_press(on_secondary_press)
129 .child(
130 rect()
131 .offset_x(margin_left)
132 .direction(Direction::Horizontal)
133 .width(Size::fill())
134 .cross_align(Alignment::center())
135 .child(rect().width(Size::px(25.)).maybe_child(arrow_button))
136 .child(
137 paragraph()
138 .max_lines(1)
139 .font_size(14.)
140 .text_overflow(TextOverflow::Ellipsis)
141 .span(
142 Span::new(if node.is_window {
143 "Window".to_string()
144 } else if role == AccessibilityRole::GenericContainer {
145 "rect".to_string()
146 } else {
147 format!("{role:?}")
148 })
149 .color(Color::WHITE),
150 )
151 .span(
152 Span::new(if node.is_window {
153 format!(", id: {}", self.window_id)
154 } else {
155 format!(", id: {}", id)
156 })
157 .color(Color::from_rgb(200, 200, 200)),
158 ),
159 ),
160 )
161 .into()
162 }
163}