Skip to main content

fret_ui/tree/
ui_tree_widget.rs

1use super::*;
2
3impl<H: UiHost> UiTree<H> {
4    pub fn cleanup_subtree(&mut self, services: &mut dyn UiServices, root: NodeId) {
5        // Avoid recursion: deep trees can overflow the stack during cleanup.
6        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        // Build the chain from target -> root, then walk root -> target.
243        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        // Top-left corner
332        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        // Top-right corner
341        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        // Bottom-right corner
350        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        // Bottom-left corner
359        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}