Skip to main content

fret_ui/tree/layers/
impls.rs

1use super::super::*;
2use super::{OverlayRootOptions, UiLayer, UiLayerId};
3
4impl<H: UiHost> UiTree<H> {
5    /// Returns the current UI layer order in paint order (back-to-front).
6    ///
7    /// This includes the base layer and any overlay layers (even if currently invisible).
8    pub fn layer_ids_in_paint_order(&self) -> &[UiLayerId] {
9        self.layer_order.as_slice()
10    }
11
12    /// Reorders layers in paint order (back-to-front).
13    ///
14    /// This is a mechanism-only API intended for component-layer overlay orchestration. Policy
15    /// code should treat this as "stable z-order correction" rather than a per-component knob.
16    ///
17    /// Notes:
18    /// - The base layer (when present) is always kept at the back (index 0).
19    /// - Unknown/missing layer IDs are ignored, and missing existing layers are appended in their
20    ///   previous relative order.
21    pub fn reorder_layers_in_paint_order(&mut self, desired: Vec<UiLayerId>) {
22        if self.layer_order.is_empty() {
23            return;
24        }
25
26        let mut seen = std::collections::HashSet::<UiLayerId>::new();
27        let mut next: Vec<UiLayerId> = Vec::with_capacity(self.layer_order.len());
28
29        for id in desired {
30            if !self.layers.contains_key(id) {
31                continue;
32            }
33            if !seen.insert(id) {
34                continue;
35            }
36            next.push(id);
37        }
38
39        // Preserve any layers not mentioned by the caller in their existing relative order.
40        for &id in &self.layer_order {
41            if !self.layers.contains_key(id) {
42                continue;
43            }
44            if seen.insert(id) {
45                next.push(id);
46            }
47        }
48
49        if let Some(base) = self.base_layer {
50            next.retain(|&id| id != base);
51            next.insert(0, base);
52        }
53
54        if next == self.layer_order {
55            return;
56        }
57
58        self.layer_order = next;
59        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
60
61        // Layer order changes can move the active modal/focus barriers. Ensure focus/capture do
62        // not remain under a barrier after reordering.
63        let (active_roots, barrier_root) = self.active_input_layers();
64        if barrier_root.is_some() {
65            self.enforce_modal_barrier_scope(&active_roots);
66        }
67        let (active_focus_roots, focus_barrier_root) = self.active_focus_layers();
68        if focus_barrier_root.is_some() {
69            self.enforce_focus_barrier_scope(&active_focus_roots);
70        }
71    }
72
73    pub(in crate::tree) fn enforce_modal_barrier_scope(&mut self, active_roots: &[NodeId]) {
74        let (focus_roots, focus_barrier_root) = self.active_focus_layers();
75        if focus_barrier_root.is_some()
76            && self.focus.is_some_and(|n| {
77                !self.is_reachable_from_any_root_via_children(n, focus_roots.as_slice())
78            })
79        {
80            self.set_focus_unchecked(None, "layers: enforce modal barrier scope");
81        }
82        let to_remove: Vec<PointerId> = self
83            .captured
84            .iter()
85            .filter_map(|(p, n)| {
86                (!self.is_reachable_from_any_root_via_children(*n, active_roots)).then_some(*p)
87            })
88            .collect();
89        for p in to_remove {
90            self.captured.remove(&p);
91        }
92    }
93
94    pub(in crate::tree) fn enforce_focus_barrier_scope(&mut self, active_roots: &[NodeId]) {
95        if self
96            .focus
97            .is_some_and(|n| !self.is_reachable_from_any_root_via_children(n, active_roots))
98        {
99            self.set_focus_unchecked(None, "layers: enforce focus barrier scope");
100        }
101    }
102
103    pub(in crate::tree) fn prune_interaction_state_outside_active_layers(
104        &mut self,
105        focus_reason: &'static str,
106    ) {
107        let (active_input_roots, _) = self.active_input_layers();
108        let to_remove: Vec<PointerId> = self
109            .captured
110            .iter()
111            .filter_map(|(pointer, node)| {
112                (!self
113                    .is_reachable_from_any_root_via_children(*node, active_input_roots.as_slice()))
114                .then_some(*pointer)
115            })
116            .collect();
117        for pointer in to_remove {
118            self.captured.remove(&pointer);
119        }
120
121        let (active_focus_roots, _) = self.active_focus_layers();
122        if self.focus.is_some_and(|node| {
123            !self.is_reachable_from_any_root_via_children(node, active_focus_roots.as_slice())
124        }) {
125            self.set_focus_unchecked(None, focus_reason);
126        }
127    }
128
129    pub fn base_root(&self) -> Option<NodeId> {
130        self.base_layer
131            .and_then(|id| self.layers.get(id).map(|l| l.root))
132    }
133
134    pub fn set_base_root(&mut self, root: NodeId) -> UiLayerId {
135        if let Some(id) = self.base_layer {
136            self.update_layer_root(id, root);
137            return id;
138        }
139
140        let id = self.layers.insert(UiLayer {
141            root,
142            visible: true,
143            blocks_underlay_input: false,
144            blocks_underlay_focus: false,
145            hit_testable: true,
146            pointer_occlusion: PointerOcclusion::None,
147            wants_pointer_down_outside_events: false,
148            consume_pointer_down_outside_events: false,
149            pointer_down_outside_branches: Vec::new(),
150            scroll_dismiss_elements: Vec::new(),
151            wants_pointer_move_events: false,
152            wants_timer_events: true,
153        });
154        self.root_to_layer.insert(root, id);
155        self.layer_order.insert(0, id);
156        self.base_layer = Some(id);
157        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
158        id
159    }
160
161    pub fn push_overlay_root(&mut self, root: NodeId, blocks_underlay_input: bool) -> UiLayerId {
162        self.push_overlay_root_with_options(root, OverlayRootOptions::new(blocks_underlay_input))
163    }
164
165    pub fn push_overlay_root_with_options(
166        &mut self,
167        root: NodeId,
168        options: OverlayRootOptions,
169    ) -> UiLayerId {
170        let id = self.layers.insert(UiLayer {
171            root,
172            visible: true,
173            blocks_underlay_input: options.blocks_underlay_input,
174            blocks_underlay_focus: options.blocks_underlay_input,
175            hit_testable: options.hit_testable,
176            pointer_occlusion: PointerOcclusion::None,
177            wants_pointer_down_outside_events: false,
178            consume_pointer_down_outside_events: false,
179            pointer_down_outside_branches: Vec::new(),
180            scroll_dismiss_elements: Vec::new(),
181            wants_pointer_move_events: false,
182            wants_timer_events: false,
183        });
184        self.root_to_layer.insert(root, id);
185        self.layer_order.push(id);
186        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
187
188        if options.blocks_underlay_input {
189            let (active_roots, _barrier_root) = self.active_input_layers();
190            self.enforce_modal_barrier_scope(&active_roots);
191        }
192
193        id
194    }
195
196    /// Uninstalls an overlay layer and removes its root subtree.
197    ///
198    /// This is the symmetric operation to `push_overlay_root(...)` /
199    /// `push_overlay_root_with_options(...)` and exists to keep the overlay substrate contract
200    /// minimal but complete (ADR 0066).
201    ///
202    /// Notes:
203    /// - The base layer cannot be removed (use `set_base_root` instead).
204    /// - This removes the layer root node, and recursively removes its children **unless** a child
205    ///   subtree is itself a layer root (which is treated as an independent root).
206    pub fn remove_layer(
207        &mut self,
208        services: &mut dyn UiServices,
209        layer: UiLayerId,
210    ) -> Option<NodeId> {
211        if self.base_layer == Some(layer) {
212            return None;
213        }
214        let root = self.layers.get(layer).map(|l| l.root)?;
215
216        // Make the root removable by the existing subtree removal logic (which normally refuses to
217        // delete layer roots).
218        self.root_to_layer.remove(&root);
219
220        self.layer_order.retain(|&id| id != layer);
221        let _ = self.layers.remove(layer);
222        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
223
224        let mut removed: Vec<NodeId> = Vec::new();
225        self.remove_subtree_inner(services, root, &mut removed);
226
227        Some(root)
228    }
229
230    #[track_caller]
231    pub fn set_layer_visible(&mut self, layer: UiLayerId, visible: bool) {
232        let prev_visible = self.layers.get(layer).map(|l| l.visible);
233        let Some(l) = self.layers.get_mut(layer) else {
234            return;
235        };
236        l.visible = visible;
237
238        if !visible {
239            let to_remove: Vec<fret_core::PointerId> = self
240                .captured
241                .iter()
242                .filter_map(|(p, n)| {
243                    (self.node_layer(*n).is_some_and(|lid| lid == layer)).then_some(*p)
244                })
245                .collect();
246            for p in to_remove {
247                self.captured.remove(&p);
248            }
249            if self
250                .focus
251                .is_some_and(|n| self.node_layer(n).is_some_and(|lid| lid == layer))
252            {
253                self.set_focus_unchecked(None, "layers: set_layer_visible(false)");
254            }
255        }
256
257        // When visibility changes, the active modal barrier can appear/disappear or move. Ensure
258        // focus/capture do not remain in layers that are now under the barrier (or otherwise
259        // inactive).
260        //
261        // This is especially important for overlay managers that reuse layer roots and toggle
262        // visibility instead of creating/removing roots each time (fearless refactors should keep
263        // the behavior consistent).
264        if prev_visible != Some(visible) {
265            self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
266            let (active_roots, barrier_root) = self.active_input_layers();
267            if barrier_root.is_some() {
268                self.enforce_modal_barrier_scope(&active_roots);
269            }
270
271            #[cfg(feature = "diagnostics")]
272            if self.debug_enabled {
273                let caller = std::panic::Location::caller();
274                self.debug_layer_visible_writes
275                    .push(UiDebugSetLayerVisibleWrite {
276                        layer,
277                        frame_id: self.debug_stats.frame_id,
278                        prev_visible,
279                        visible,
280                        file: caller.file(),
281                        line: caller.line(),
282                        column: caller.column(),
283                    });
284            }
285        }
286    }
287
288    pub fn set_layer_hit_testable(&mut self, layer: UiLayerId, hit_testable: bool) {
289        let prev_hit_testable = self.layers.get(layer).map(|l| l.hit_testable);
290        let Some(l) = self.layers.get_mut(layer) else {
291            return;
292        };
293        l.hit_testable = hit_testable;
294
295        if !hit_testable {
296            let to_remove: Vec<fret_core::PointerId> = self
297                .captured
298                .iter()
299                .filter_map(|(p, n)| {
300                    (self.node_layer(*n).is_some_and(|lid| lid == layer)).then_some(*p)
301                })
302                .collect();
303            for p in to_remove {
304                self.captured.remove(&p);
305            }
306            if self
307                .focus
308                .is_some_and(|n| self.node_layer(n).is_some_and(|lid| lid == layer))
309            {
310                self.set_focus_unchecked(None, "layers: set_layer_hit_testable(false)");
311            }
312        }
313
314        if prev_hit_testable != Some(hit_testable) {
315            self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
316            let (active_roots, barrier_root) = self.active_input_layers();
317            if barrier_root.is_some() {
318                self.enforce_modal_barrier_scope(&active_roots);
319            }
320        }
321    }
322
323    pub fn set_layer_pointer_occlusion(&mut self, layer: UiLayerId, occlusion: PointerOcclusion) {
324        let Some(l) = self.layers.get_mut(layer) else {
325            return;
326        };
327        if l.pointer_occlusion == occlusion {
328            return;
329        }
330        l.pointer_occlusion = occlusion;
331        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
332    }
333
334    pub fn set_layer_blocks_underlay_focus(&mut self, layer: UiLayerId, blocks: bool) {
335        let prev = self.layers.get(layer).map(|l| l.blocks_underlay_focus);
336        let Some(l) = self.layers.get_mut(layer) else {
337            return;
338        };
339        l.blocks_underlay_focus = blocks;
340
341        if prev != Some(blocks) {
342            self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
343            let (active_roots, barrier_root) = self.active_focus_layers();
344            if barrier_root.is_some() {
345                self.enforce_focus_barrier_scope(&active_roots);
346            }
347        }
348    }
349
350    pub fn is_layer_visible(&self, layer: UiLayerId) -> bool {
351        self.layers.get(layer).is_some_and(|l| l.visible)
352    }
353
354    pub fn layer_root(&self, layer: UiLayerId) -> Option<NodeId> {
355        self.layers.get(layer).map(|l| l.root)
356    }
357
358    pub(crate) fn all_layer_roots(&self) -> Vec<NodeId> {
359        self.layer_order
360            .iter()
361            .filter_map(|layer| self.layers.get(*layer).map(|l| l.root))
362            .collect()
363    }
364
365    pub fn set_layer_wants_pointer_move_events(&mut self, layer: UiLayerId, wants: bool) {
366        let Some(l) = self.layers.get_mut(layer) else {
367            return;
368        };
369        if l.wants_pointer_move_events == wants {
370            return;
371        }
372        l.wants_pointer_move_events = wants;
373        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
374    }
375
376    pub fn set_layer_wants_pointer_down_outside_events(&mut self, layer: UiLayerId, wants: bool) {
377        let Some(l) = self.layers.get_mut(layer) else {
378            return;
379        };
380        if l.wants_pointer_down_outside_events == wants {
381            return;
382        }
383        l.wants_pointer_down_outside_events = wants;
384        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
385    }
386
387    pub fn set_layer_consume_pointer_down_outside_events(
388        &mut self,
389        layer: UiLayerId,
390        consume: bool,
391    ) {
392        let Some(l) = self.layers.get_mut(layer) else {
393            return;
394        };
395        if l.consume_pointer_down_outside_events == consume {
396            return;
397        }
398        l.consume_pointer_down_outside_events = consume;
399        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
400    }
401
402    pub fn set_layer_pointer_down_outside_branches(
403        &mut self,
404        layer: UiLayerId,
405        branches: Vec<NodeId>,
406    ) {
407        let Some(l) = self.layers.get_mut(layer) else {
408            return;
409        };
410        if l.pointer_down_outside_branches == branches {
411            return;
412        }
413        l.pointer_down_outside_branches = branches;
414        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
415    }
416
417    /// Register elements that should dismiss this overlay when a scroll event targets an ancestor
418    /// of any element's current node.
419    ///
420    /// This is intended for Radix-aligned tooltip behavior: when the tooltip trigger is scrolled,
421    /// the tooltip should close (Radix closes when `event.target.contains(trigger)` on scroll).
422    pub fn set_layer_scroll_dismiss_elements(
423        &mut self,
424        layer: UiLayerId,
425        elements: Vec<crate::GlobalElementId>,
426    ) {
427        let Some(l) = self.layers.get_mut(layer) else {
428            return;
429        };
430        if l.scroll_dismiss_elements == elements {
431            return;
432        }
433        l.scroll_dismiss_elements = elements;
434        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
435    }
436
437    pub fn set_layer_wants_timer_events(&mut self, layer: UiLayerId, wants: bool) {
438        let Some(l) = self.layers.get_mut(layer) else {
439            return;
440        };
441        if l.wants_timer_events == wants {
442            return;
443        }
444        l.wants_timer_events = wants;
445        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
446    }
447
448    pub fn node_layer(&self, node: NodeId) -> Option<UiLayerId> {
449        let root = self.node_root(node)?;
450        self.root_to_layer.get(&root).copied()
451    }
452
453    pub(in crate::tree) fn visible_layers_in_paint_order(
454        &self,
455    ) -> impl Iterator<Item = UiLayerId> + '_ {
456        self.layer_order
457            .iter()
458            .copied()
459            .filter(|id| self.layers.get(*id).is_some_and(|l| l.visible))
460    }
461
462    pub(in crate::tree) fn topmost_pointer_occlusion_layer(
463        &self,
464        barrier_root: Option<NodeId>,
465    ) -> Option<(UiLayerId, PointerOcclusion)> {
466        let mut hit_barrier = false;
467        for &layer_id in self.layer_order.iter().rev() {
468            let Some(layer) = self.layers.get(layer_id) else {
469                continue;
470            };
471            if !layer.visible {
472                continue;
473            }
474            if barrier_root.is_some() && hit_barrier {
475                break;
476            }
477
478            let occlusion = layer.pointer_occlusion;
479            if occlusion != PointerOcclusion::None {
480                return Some((layer_id, occlusion));
481            }
482
483            if barrier_root == Some(layer.root) {
484                hit_barrier = true;
485            }
486        }
487        None
488    }
489
490    pub(in crate::tree) fn active_input_layers(&self) -> (Vec<NodeId>, Option<NodeId>) {
491        let mut any_visible = false;
492        let mut barrier_root: Option<NodeId> = None;
493        for &layer_id in &self.layer_order {
494            let Some(layer) = self.layers.get(layer_id) else {
495                continue;
496            };
497            if !layer.visible {
498                continue;
499            }
500            any_visible = true;
501
502            // Modal/pointer barriers can be hit-test-inert (e.g. close transitions, pointer-only
503            // underlay blocking). A barrier must still gate input even when it isn't hit-testable.
504            if layer.blocks_underlay_input {
505                barrier_root = Some(layer.root);
506            }
507        }
508
509        if !any_visible {
510            return (Vec::new(), None);
511        }
512
513        let mut roots: Vec<NodeId> = Vec::new();
514        for &layer_id in self.layer_order.iter().rev() {
515            let Some(layer) = self.layers.get(layer_id) else {
516                continue;
517            };
518            if !layer.visible {
519                continue;
520            }
521
522            // Focus barriers can become active before a layer is hit-testable (e.g. during open/close
523            // transitions). Include the barrier root itself so focus can still be moved into the
524            // barrier scope; otherwise `set_focus` can reject all targets while the underlay is
525            // blocked, leading to spurious focus loss.
526            if layer.hit_testable || barrier_root == Some(layer.root) {
527                roots.push(layer.root);
528            }
529
530            if barrier_root == Some(layer.root) {
531                break;
532            }
533        }
534        (roots, barrier_root)
535    }
536
537    pub(in crate::tree) fn active_pointer_down_outside_layer_roots(
538        &self,
539        barrier_root: Option<NodeId>,
540    ) -> Vec<NodeId> {
541        let mut any_visible = false;
542        for &layer_id in &self.layer_order {
543            let Some(layer) = self.layers.get(layer_id) else {
544                continue;
545            };
546            if !layer.visible {
547                continue;
548            }
549            any_visible = true;
550            if layer.blocks_underlay_input {
551                break;
552            }
553        }
554
555        if !any_visible {
556            return Vec::new();
557        }
558
559        let mut roots: Vec<NodeId> = Vec::new();
560        for &layer_id in self.layer_order.iter().rev() {
561            let Some(layer) = self.layers.get(layer_id) else {
562                continue;
563            };
564            if !layer.visible {
565                continue;
566            }
567
568            roots.push(layer.root);
569
570            if barrier_root == Some(layer.root) {
571                break;
572            }
573        }
574        roots
575    }
576
577    pub(in crate::tree) fn active_focus_layers(&self) -> (Vec<NodeId>, Option<NodeId>) {
578        let mut any_visible = false;
579        let mut barrier_root: Option<NodeId> = None;
580        let focused_layer = self.focus.and_then(|node| self.node_layer(node));
581        for &layer_id in &self.layer_order {
582            let Some(layer) = self.layers.get(layer_id) else {
583                continue;
584            };
585            if !layer.visible {
586                continue;
587            }
588            any_visible = true;
589
590            if layer.blocks_underlay_focus {
591                barrier_root = Some(layer.root);
592            }
593        }
594
595        if !any_visible {
596            return (Vec::new(), None);
597        }
598
599        let mut roots: Vec<NodeId> = Vec::new();
600        for &layer_id in self.layer_order.iter().rev() {
601            let Some(layer) = self.layers.get(layer_id) else {
602                continue;
603            };
604            if !layer.visible {
605                continue;
606            }
607
608            // Focus barriers can become active while the barrier layer is hit-test-inert (e.g.
609            // open/close transitions or pointer-only underlay blocking). Include the barrier root
610            // itself so focus can remain inside (and be moved within) the barrier scope.
611            //
612            // Likewise, if focus already lives inside a visible layer, keep that layer in the
613            // authoritative focus snapshot even when pointer arbitration temporarily makes it
614            // hit-test-inert. Otherwise same-frame focus repair can self-invalidate by resolving a
615            // live node and then immediately pruning it out of the active focus roots.
616            if layer.hit_testable
617                || barrier_root == Some(layer.root)
618                || focused_layer == Some(layer_id)
619            {
620                roots.push(layer.root);
621            }
622
623            if barrier_root == Some(layer.root) {
624                break;
625            }
626        }
627        (roots, barrier_root)
628    }
629
630    fn update_layer_root(&mut self, layer: UiLayerId, root: NodeId) {
631        let Some(old_root) = self.layers.get(layer).map(|layer| layer.root) else {
632            return;
633        };
634        if old_root == root {
635            return;
636        }
637
638        self.root_to_layer.remove(&old_root);
639        let Some(layer_entry) = self.layers.get_mut(layer) else {
640            return;
641        };
642        layer_entry.root = root;
643        self.root_to_layer.insert(root, layer);
644        self.prune_interaction_state_outside_active_layers("layers: update_layer_root");
645        self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
646    }
647}