Skip to main content

fret_ui/tree/
ui_tree_focus.rs

1use super::*;
2
3impl<H: UiHost> UiTree<H> {
4    pub fn set_window(&mut self, window: AppWindowId) {
5        self.window = Some(window);
6    }
7
8    pub fn focus(&self) -> Option<NodeId> {
9        self.focus
10    }
11
12    /// Request focus for a declarative element.
13    ///
14    /// If the target element is already attached to a live node, focus moves immediately. If the
15    /// target is being rebuilt and its node is not yet attached, the request is retained and
16    /// retried at later authoritative same-frame boundaries (window snapshot publish / final
17    /// layout), so policy code does not need to guess whether the node is "ready" yet.
18    pub fn request_focus_element(&mut self, app: &mut H, target: GlobalElementId) {
19        if let Some(node) = self.resolve_live_attached_node_for_element(app, self.window, target) {
20            let before = self.focus;
21            self.set_focus(Some(node));
22            if self.focus == Some(node) {
23                self.pending_focus_target = None;
24            } else if before != Some(node) {
25                self.pending_focus_target = Some(target);
26            }
27        } else {
28            self.pending_focus_target = Some(target);
29        }
30    }
31
32    pub(in crate::tree) fn resolve_pending_focus_target_if_needed(&mut self, app: &mut H) -> bool {
33        let Some(target) = self.pending_focus_target else {
34            return false;
35        };
36
37        if let Some(node) = self.resolve_live_attached_node_for_element(app, self.window, target) {
38            let before = self.focus;
39            self.set_focus(Some(node));
40            if self.focus == Some(node) {
41                self.pending_focus_target = None;
42                return self.focus != before;
43            }
44            return false;
45        }
46
47        if let Some(window) = self.window
48            && !crate::elements::element_identity_is_live_in_current_frame(app, window, target)
49        {
50            self.pending_focus_target = None;
51        }
52
53        false
54    }
55
56    #[track_caller]
57    pub fn set_focus(&mut self, focus: Option<NodeId>) {
58        #[cfg(debug_assertions)]
59        let debug_focus_scope = std::env::var_os("FRET_TEST_DEBUG_FOCUS_SCOPE").is_some();
60        #[cfg(debug_assertions)]
61        if debug_focus_scope && self.focus != focus {
62            let loc = std::panic::Location::caller();
63            eprintln!(
64                "debug: set_focus at {}:{}:{}: {:?} -> {:?}",
65                loc.file(),
66                loc.line(),
67                loc.column(),
68                self.focus,
69                focus
70            );
71        }
72
73        if let Some(focus) = focus {
74            let (active_roots, barrier_root) = self.active_focus_layers();
75            if barrier_root.is_some()
76                && !self.is_reachable_from_any_root_via_children(focus, active_roots.as_slice())
77            {
78                return;
79            }
80        }
81        if self.focus != focus {
82            self.ime_composing = false;
83        }
84        let changed = self.focus != focus;
85        self.focus = focus;
86        if focus.is_some() {
87            self.pending_focus_target = None;
88        }
89        if changed {
90            self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
91        }
92    }
93
94    /// Internal focus mutation helper that skips focus-barrier gating.
95    ///
96    /// This is used by mechanism code that must clear or adjust focus (e.g. scope enforcement)
97    /// without re-entering policy checks.
98    #[track_caller]
99    pub(in crate::tree) fn set_focus_unchecked(
100        &mut self,
101        focus: Option<NodeId>,
102        reason: &'static str,
103    ) {
104        #[cfg(debug_assertions)]
105        {
106            let debug_focus_scope = std::env::var_os("FRET_TEST_DEBUG_FOCUS_SCOPE").is_some();
107            if debug_focus_scope && self.focus != focus {
108                let loc = std::panic::Location::caller();
109                eprintln!(
110                    "debug: set_focus_unchecked({reason}) at {}:{}:{}: {:?} -> {:?}",
111                    loc.file(),
112                    loc.line(),
113                    loc.column(),
114                    self.focus,
115                    focus
116                );
117            }
118        }
119
120        if self.focus != focus {
121            self.ime_composing = false;
122        }
123        let changed = self.focus != focus;
124        self.focus = focus;
125        if focus.is_some() {
126            self.pending_focus_target = None;
127        }
128        if changed {
129            self.request_post_layout_window_runtime_snapshot_refine_if_layout_active();
130        }
131    }
132
133    const TOUCH_POINTER_DOWN_OUTSIDE_SLOP_PX: f32 = 6.0;
134
135    pub(in crate::tree) fn update_touch_pointer_down_outside_move(
136        &mut self,
137        pointer_id: PointerId,
138        position: Point,
139    ) {
140        let Some(candidate) = self
141            .touch_pointer_down_outside_candidates
142            .get_mut(&pointer_id)
143        else {
144            return;
145        };
146        if candidate.moved {
147            return;
148        }
149        let dx = position.x.0 - candidate.start_pos.x.0;
150        let dy = position.y.0 - candidate.start_pos.y.0;
151        if (dx * dx + dy * dy).sqrt() > Self::TOUCH_POINTER_DOWN_OUTSIDE_SLOP_PX {
152            candidate.moved = true;
153        }
154    }
155}