fret_ui/tree/
ui_tree_widget.rs1use super::*;
2
3impl<H: UiHost> UiTree<H> {
4 pub fn cleanup_subtree(&mut self, services: &mut dyn UiServices, root: NodeId) {
5 let mut stack: Vec<NodeId> = vec![root];
7 while let Some(node) = stack.pop() {
8 let Some(n) = self.nodes.get(node) else {
9 continue;
10 };
11 let children = n.children.clone();
12 for child in children {
13 stack.push(child);
14 }
15
16 self.cleanup_node_resources(services, node);
17 }
18 }
19
20 pub(crate) fn set_node_text_boundary_mode_override(
21 &mut self,
22 node: NodeId,
23 mode: Option<fret_runtime::TextBoundaryMode>,
24 ) {
25 if let Some(n) = self.nodes.get_mut(node) {
26 n.text_boundary_mode_override = mode;
27 }
28 }
29
30 pub(in crate::tree) fn focus_text_boundary_mode_override(
31 &self,
32 ) -> Option<fret_runtime::TextBoundaryMode> {
33 let focus = self.focus?;
34 self.nodes
35 .get(focus)
36 .and_then(|n| n.text_boundary_mode_override)
37 }
38
39 pub(in crate::tree) fn cleanup_node_resources(
40 &mut self,
41 services: &mut dyn UiServices,
42 node: NodeId,
43 ) {
44 let widget = self.nodes.get_mut(node).and_then(|n| n.widget.take());
45 if let Some(mut widget) = widget {
46 widget.cleanup_resources(services);
47 if let Some(n) = self.nodes.get_mut(node) {
48 n.widget = Some(widget);
49 } else {
50 self.deferred_cleanup.push(widget);
51 }
52 }
53 }
54
55 #[track_caller]
56 pub(in crate::tree) fn with_widget_mut<R: Default>(
57 &mut self,
58 node: NodeId,
59 f: impl FnOnce(&mut dyn Widget<H>, &mut UiTree<H>) -> R,
60 ) -> R {
61 fn warn_with_widget_mut_failure_once(node: NodeId, reason: &'static str) {
62 static SEEN: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
63
64 let caller = Location::caller();
65 let key = format!(
66 "{reason}:{node:?}:{}:{}:{}",
67 caller.file(),
68 caller.line(),
69 caller.column()
70 );
71
72 let seen = SEEN.get_or_init(|| Mutex::new(HashSet::new()));
73 let first = match seen.lock() {
74 Ok(mut guard) => guard.insert(key),
75 Err(_) => true,
76 };
77
78 if first {
79 tracing::error!(
80 ?node,
81 reason,
82 file = caller.file(),
83 line = caller.line(),
84 column = caller.column(),
85 "UiTree widget access failed; returning default"
86 );
87 }
88 }
89
90 let Some(n) = self.nodes.get_mut(node) else {
91 if crate::strict_runtime::strict_runtime_enabled() {
92 let caller = Location::caller();
93 panic!(
94 "UiTree::with_widget_mut: node missing: {node:?} at {}:{}:{}",
95 caller.file(),
96 caller.line(),
97 caller.column()
98 );
99 }
100
101 warn_with_widget_mut_failure_once(node, "node_missing");
102 return R::default();
103 };
104
105 let Some(widget) = n.widget.take() else {
106 if crate::strict_runtime::strict_runtime_enabled() {
107 let caller = Location::caller();
108 panic!(
109 "UiTree::with_widget_mut: widget missing (re-entrant borrow?): {node:?} at {}:{}:{}",
110 caller.file(),
111 caller.line(),
112 caller.column()
113 );
114 }
115
116 warn_with_widget_mut_failure_once(node, "widget_missing");
117 return R::default();
118 };
119
120 let mut widget = widget;
121 let result = catch_unwind(AssertUnwindSafe(|| f(widget.as_mut(), self)));
122
123 if let Some(n) = self.nodes.get_mut(node) {
124 n.widget = Some(widget);
125 } else {
126 self.deferred_cleanup.push(widget);
127 }
128
129 match result {
130 Ok(result) => result,
131 Err(payload) => resume_unwind(payload),
132 }
133 }
134
135 pub(crate) fn sync_interactivity_gate_widget(
136 &mut self,
137 node: NodeId,
138 present: bool,
139 interactive: bool,
140 ) {
141 if self
142 .nodes
143 .get(node)
144 .and_then(|n| n.widget.as_ref())
145 .is_none()
146 {
147 return;
148 }
149 #[cfg(debug_assertions)]
150 if crate::runtime_config::ui_runtime_config().debug_interactivity_gate_sync {
151 eprintln!(
152 "sync_interactivity_gate_widget: node={node:?} present={present} interactive={interactive}"
153 );
154 }
155 self.with_widget_mut(node, |w, _ui| {
156 w.sync_interactivity_gate(present, interactive);
157 });
158 }
159
160 pub(crate) fn sync_hit_test_gate_widget(&mut self, node: NodeId, hit_test: bool) {
161 if self
162 .nodes
163 .get(node)
164 .and_then(|n| n.widget.as_ref())
165 .is_none()
166 {
167 return;
168 }
169 #[cfg(debug_assertions)]
170 if crate::runtime_config::ui_runtime_config().debug_hit_test_gate_sync {
171 eprintln!("sync_hit_test_gate_widget: node={node:?} hit_test={hit_test}");
172 }
173 self.with_widget_mut(node, |w, _ui| {
174 w.sync_hit_test_gate(hit_test);
175 });
176 }
177
178 pub(crate) fn sync_focus_traversal_gate_widget(&mut self, node: NodeId, traverse: bool) {
179 if self
180 .nodes
181 .get(node)
182 .and_then(|n| n.widget.as_ref())
183 .is_none()
184 {
185 return;
186 }
187 #[cfg(debug_assertions)]
188 if crate::runtime_config::ui_runtime_config().debug_focus_traversal_gate_sync {
189 eprintln!("sync_focus_traversal_gate_widget: node={node:?} traverse={traverse}");
190 }
191 self.with_widget_mut(node, |w, _ui| {
192 w.sync_focus_traversal_gate(traverse);
193 });
194 }
195
196 pub(in crate::tree) fn node_render_transform(&self, node: NodeId) -> Option<Transform2D> {
197 let n = self.nodes.get(node)?;
198 let w = n.widget.as_ref()?;
199 let t = w.render_transform(n.bounds)?;
200 t.inverse().is_some().then_some(t)
201 }
202
203 pub(crate) fn node_children_render_transform(&self, node: NodeId) -> Option<Transform2D> {
204 let n = self.nodes.get(node)?;
205 let w = n.widget.as_ref()?;
206 let t = w.children_render_transform(n.bounds)?;
207 t.inverse().is_some().then_some(t)
208 }
209
210 pub(in crate::tree) fn apply_vector(t: Transform2D, v: Point) -> Point {
211 Point::new(Px(t.a * v.x.0 + t.c * v.y.0), Px(t.b * v.x.0 + t.d * v.y.0))
212 }
213
214 pub(crate) fn map_window_point_to_node_layout_space(
215 &self,
216 target: NodeId,
217 window_pos: Point,
218 ) -> Option<Point> {
219 self.map_window_point_and_vector_to_node_layout_space(target, window_pos, None)
220 .map(|(p, _)| p)
221 }
222
223 pub(crate) fn map_window_vector_to_node_layout_space(
224 &self,
225 target: NodeId,
226 window_vec: Point,
227 ) -> Option<Point> {
228 self.map_window_point_and_vector_to_node_layout_space(
229 target,
230 Point::new(Px(0.0), Px(0.0)),
231 Some(window_vec),
232 )
233 .map(|(_, v)| v.unwrap_or(window_vec))
234 }
235
236 fn map_window_point_and_vector_to_node_layout_space(
237 &self,
238 target: NodeId,
239 mut mapped_pos: Point,
240 mut mapped_vec: Option<Point>,
241 ) -> Option<(Point, Option<Point>)> {
242 let mut chain: Vec<NodeId> = Vec::new();
244 let mut cur = Some(target);
245 while let Some(id) = cur {
246 chain.push(id);
247 cur = self.nodes.get(id).and_then(|n| n.parent);
248 }
249 if chain.is_empty() {
250 return None;
251 }
252 chain.reverse();
253
254 for (idx, &node) in chain.iter().enumerate() {
255 let is_target = idx == chain.len().saturating_sub(1);
256
257 let prepaint = self
258 .nodes
259 .get(node)
260 .and_then(|n| {
261 (!self.inspection_active && !n.invalidation.hit_test)
262 .then_some(n.prepaint_hit_test)
263 })
264 .flatten();
265
266 if let Some(inv) = prepaint
267 .and_then(|p| p.render_transform_inv)
268 .or_else(|| self.node_render_transform(node).and_then(|t| t.inverse()))
269 {
270 mapped_pos = inv.apply_point(mapped_pos);
271 if let Some(v) = mapped_vec {
272 mapped_vec = Some(Self::apply_vector(inv, v));
273 }
274 }
275
276 if is_target {
277 break;
278 }
279
280 let prepaint = self
281 .nodes
282 .get(node)
283 .and_then(|n| {
284 (!self.inspection_active && !n.invalidation.hit_test)
285 .then_some(n.prepaint_hit_test)
286 })
287 .flatten();
288 if let Some(inv) = prepaint
289 .and_then(|p| p.children_render_transform_inv)
290 .or_else(|| {
291 self.node_children_render_transform(node)
292 .and_then(|t| t.inverse())
293 })
294 {
295 mapped_pos = inv.apply_point(mapped_pos);
296 if let Some(v) = mapped_vec {
297 mapped_vec = Some(Self::apply_vector(inv, v));
298 }
299 }
300 }
301
302 Some((mapped_pos, mapped_vec))
303 }
304
305 pub(in crate::tree) fn point_in_rounded_rect(
306 bounds: Rect,
307 radii: Corners,
308 position: Point,
309 ) -> bool {
310 if !bounds.contains(position) {
311 return false;
312 }
313
314 let w = bounds.size.width.0.max(0.0);
315 let h = bounds.size.height.0.max(0.0);
316 let limit = 0.5 * w.min(h);
317
318 let tl = Px(radii.top_left.0.max(0.0).min(limit));
319 let tr = Px(radii.top_right.0.max(0.0).min(limit));
320 let br = Px(radii.bottom_right.0.max(0.0).min(limit));
321 let bl = Px(radii.bottom_left.0.max(0.0).min(limit));
322
323 let left = bounds.origin.x.0;
324 let top = bounds.origin.y.0;
325 let right = left + w;
326 let bottom = top + h;
327
328 let x = position.x.0;
329 let y = position.y.0;
330
331 if tl.0 > 0.0 && x < left + tl.0 && y < top + tl.0 {
333 let cx = left + tl.0;
334 let cy = top + tl.0;
335 let dx = x - cx;
336 let dy = y - cy;
337 return dx * dx + dy * dy <= tl.0 * tl.0;
338 }
339
340 if tr.0 > 0.0 && x > right - tr.0 && y < top + tr.0 {
342 let cx = right - tr.0;
343 let cy = top + tr.0;
344 let dx = x - cx;
345 let dy = y - cy;
346 return dx * dx + dy * dy <= tr.0 * tr.0;
347 }
348
349 if br.0 > 0.0 && x > right - br.0 && y > bottom - br.0 {
351 let cx = right - br.0;
352 let cy = bottom - br.0;
353 let dx = x - cx;
354 let dy = y - cy;
355 return dx * dx + dy * dy <= br.0 * br.0;
356 }
357
358 if bl.0 > 0.0 && x < left + bl.0 && y > bottom - bl.0 {
360 let cx = left + bl.0;
361 let cy = bottom - bl.0;
362 let dx = x - cx;
363 let dy = y - cy;
364 return dx * dx + dy * dy <= bl.0 * bl.0;
365 }
366
367 true
368 }
369}