Skip to main content

i_slint_compiler/passes/
focus_handling.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore uncallable
5
6//! This pass follows the forward-focus property on the root element to determine the initial focus item
7//! as well as handle the forward for `focus()` calls in code.
8
9#![allow(clippy::mutable_key_type)] // Element/NamedReference keys rely on Rc<RefCell<...>> identity semantics
10
11use std::cell::RefCell;
12use std::rc::Rc;
13
14use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
15use crate::expression_tree::{BuiltinFunction, Callable, Expression};
16use crate::langtype::{ElementType, Function, Type};
17use crate::namedreference::NamedReference;
18use crate::object_tree::*;
19use by_address::ByAddress;
20use smol_str::SmolStr;
21use std::collections::{HashMap, HashSet};
22use strum::IntoEnumIterator;
23
24/// Generate setup code to pass window focus to the root item or a forwarded focus if applicable.
25pub fn call_focus_on_init(component: &Rc<Component>) {
26    if let Some(focus_call_code) =
27        call_set_focus_function(&component.root_element, None, FocusFunctionType::SetFocus)
28    {
29        component.init_code.borrow_mut().focus_setting_code.push(focus_call_code);
30    }
31}
32
33/// Remove any `forward-focus` bindings, resolve any local '.focus()' calls and create a 'focus()'
34/// function on the root element if necessary.
35pub fn replace_forward_focus_bindings_with_focus_functions(
36    doc: &Document,
37    diag: &mut BuildDiagnostics,
38) {
39    for component in doc.inner_components.iter() {
40        // Phase 1: Collect all forward-forward bindings
41        let mut local_forwards = LocalFocusForwards::collect(component, diag);
42
43        // Phase 2: Filter out focus-forward bindings that aren't callable
44        local_forwards.remove_uncallable_forwards();
45
46        // Phase 3: For `focus-forward` in the root element, create `focus()` and `clear-focus()` functions that are callable from the outside
47        local_forwards.gen_focus_functions(&component.root_element);
48
49        // Phase 3b: also for PopupWindow
50        recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
51            if elem
52                .borrow()
53                .builtin_type()
54                .is_some_and(|b| matches!(b.name.as_str(), "Window" | "PopupWindow"))
55            {
56                local_forwards.gen_focus_functions(elem);
57            }
58        });
59
60        // Phase 4: All calls to `.focus()` may need to be changed with `focus-forward` resolved or changed from the built-in
61        // SetFocusItem() call to a regular function call to the component's focus() function.
62        visit_all_expressions(component, |e, _| {
63            local_forwards.resolve_focus_calls_in_expression(e)
64        });
65    }
66}
67
68/// map all `forward-focus: some-target` bindings. The key is the element that had the binding,
69/// the target is Some(ElementRc) if it's valid. The error remove_uncallable_forwards pass will
70/// set them to None if the target is not focusable. They're not removed otherwise we'd get
71/// errors on `focus()` call sites, which isn't helpful.
72struct LocalFocusForwards<'a> {
73    forwards:
74        HashMap<ByAddress<Rc<RefCell<Element>>>, Option<(Rc<RefCell<Element>>, SourceLocation)>>,
75    diag: &'a mut BuildDiagnostics,
76}
77
78impl<'a> LocalFocusForwards<'a> {
79    fn collect(component: &Rc<Component>, diag: &'a mut BuildDiagnostics) -> Self {
80        let mut forwards = HashMap::new();
81
82        recurse_elem_no_borrow(&component.root_element, &(), &mut |elem, _| {
83            let Some(forward_focus_binding) =
84                elem.borrow_mut().bindings.remove("forward-focus").map(RefCell::into_inner)
85            else {
86                return;
87            };
88
89            let Expression::ElementReference(focus_target) =
90                super::ignore_debug_hooks(&forward_focus_binding.expression)
91            else {
92                // resolve expressions pass has produced type errors
93                debug_assert!(diag.has_errors());
94                return;
95            };
96
97            let focus_target = focus_target.upgrade().unwrap();
98            let location = forward_focus_binding.to_source_location();
99
100            if Rc::ptr_eq(elem, &focus_target) {
101                diag.push_error("forward-focus can't refer to itself".into(), &location);
102                return;
103            }
104
105            if forwards
106                .insert(ByAddress(elem.clone()), (focus_target, location.clone()).into())
107                .is_some()
108            {
109                diag.push_error(
110                    "only one forward-focus binding can point to an element".into(),
111                    &location,
112                );
113            }
114        });
115
116        Self { forwards, diag }
117    }
118
119    fn remove_uncallable_forwards(&mut self) {
120        for target_and_location in self.forwards.values_mut() {
121            let (target, source_location) = target_and_location.as_ref().unwrap();
122            if call_set_focus_function(target, None, FocusFunctionType::SetFocus).is_none() {
123                self.diag.push_error(
124                    "Cannot forward focus to unfocusable element".into(),
125                    source_location,
126                );
127                *target_and_location = None;
128            }
129        }
130    }
131
132    fn get(&self, element: &ElementRc) -> Option<(ElementRc, SourceLocation)> {
133        self.forwards.get(&ByAddress(element.clone())).cloned().flatten()
134    }
135
136    fn focus_forward_for_element(
137        &mut self,
138        element: &ElementRc,
139    ) -> Option<(ElementRc, SourceLocation)> {
140        let (mut focus_redirect, mut location) = self.get(element)?;
141
142        let mut visited: HashSet<ByAddress<Rc<RefCell<Element>>>> = HashSet::new();
143        loop {
144            if !visited.insert(ByAddress(focus_redirect.clone())) {
145                self.diag.push_error("forward-focus loop".into(), &location);
146                return None;
147            }
148            if let Some((redirect, new_location)) = self.get(&focus_redirect) {
149                focus_redirect = redirect;
150                location = new_location;
151            } else {
152                return Some((focus_redirect, location));
153            }
154        }
155    }
156
157    fn resolve_focus_calls_in_expression(&mut self, expr: &mut Expression) {
158        expr.visit_mut(|e| self.resolve_focus_calls_in_expression(e));
159
160        for focus_function in FocusFunctionType::iter() {
161            if let Expression::FunctionCall {
162                function: Callable::Builtin(builtin_function_type),
163                arguments,
164                source_location,
165            } = expr
166            {
167                if *builtin_function_type != focus_function.as_builtin_function() {
168                    continue;
169                }
170                if arguments.len() != 1 {
171                    assert!(
172                        self.diag.has_errors(),
173                        "Invalid argument generated for {} call",
174                        focus_function.name()
175                    );
176                    return;
177                }
178                if let Expression::ElementReference(weak_focus_target) = &arguments[0] {
179                    let mut focus_target = weak_focus_target.upgrade().expect(
180                            "internal compiler error: weak focus/clear-focus parameter cannot be dangling"
181                        );
182
183                    if self.forwards.contains_key(&ByAddress(focus_target.clone())) {
184                        let Some((next_focus_target, _)) =
185                            self.focus_forward_for_element(&focus_target)
186                        else {
187                            // There's no need to report an additional error that focus() can't be called. Invalid
188                            // forward-focus bindings have already diagnostics produced for them.
189                            return;
190                        };
191                        focus_target = next_focus_target;
192                    }
193
194                    if let Some(set_or_clear_focus_code) = call_set_focus_function(
195                        &focus_target,
196                        source_location.as_ref(),
197                        focus_function,
198                    ) {
199                        *expr = set_or_clear_focus_code;
200                    } else {
201                        self.diag.push_error(
202                            format!(
203                                "{}() can only be called on focusable elements",
204                                focus_function.name(),
205                            ),
206                            source_location,
207                        );
208                    }
209                }
210            }
211        }
212    }
213
214    fn gen_focus_functions(&mut self, elem: &ElementRc) {
215        if let Some((root_focus_forward, focus_forward_location)) =
216            self.focus_forward_for_element(elem)
217        {
218            for function in FocusFunctionType::iter() {
219                if let Some(set_or_clear_focus_code) = call_set_focus_function(
220                    &root_focus_forward,
221                    Some(&focus_forward_location),
222                    function,
223                ) {
224                    elem.borrow_mut().property_declarations.insert(
225                        function.name().into(),
226                        PropertyDeclaration {
227                            property_type: Type::Function(Rc::new(Function {
228                                return_type: Type::Void,
229                                args: Vec::new(),
230                                arg_names: Vec::new(),
231                            })),
232                            visibility: PropertyVisibility::Public,
233                            pure: Some(false),
234                            ..Default::default()
235                        },
236                    );
237                    elem.borrow_mut().bindings.insert(
238                        function.name().into(),
239                        RefCell::new(set_or_clear_focus_code.into()),
240                    );
241                }
242            }
243        }
244    }
245}
246
247#[derive(Copy, Clone, strum::EnumIter)]
248enum FocusFunctionType {
249    SetFocus,
250    ClearFocus,
251}
252
253impl FocusFunctionType {
254    fn name(&self) -> &'static str {
255        match self {
256            Self::SetFocus => "focus",
257            Self::ClearFocus => "clear-focus",
258        }
259    }
260
261    fn as_builtin_function(&self) -> BuiltinFunction {
262        match self {
263            Self::SetFocus => BuiltinFunction::SetFocusItem,
264            Self::ClearFocus => BuiltinFunction::ClearFocusItem,
265        }
266    }
267}
268
269fn call_set_focus_function(
270    element: &ElementRc,
271    source_location: Option<&SourceLocation>,
272    function_type: FocusFunctionType,
273) -> Option<Expression> {
274    let function_name = function_type.name();
275    let declares_focus_function = {
276        let mut element = element.clone();
277        loop {
278            if element.borrow().property_declarations.contains_key(function_name) {
279                break true;
280            }
281            let base = element.borrow().base_type.clone();
282            match base {
283                ElementType::Component(compo) => element = compo.root_element.clone(),
284                _ => break false,
285            }
286        }
287    };
288    let builtin_focus_function = element.borrow().builtin_type().is_some_and(|ty| ty.accepts_focus);
289
290    if declares_focus_function {
291        Some(Expression::FunctionCall {
292            function: Callable::Function(NamedReference::new(
293                element,
294                SmolStr::new_static(function_name),
295            )),
296            arguments: Vec::new(),
297            source_location: source_location.cloned(),
298        })
299    } else if builtin_focus_function {
300        let source_location = source_location.cloned();
301        Some(Expression::FunctionCall {
302            function: function_type.as_builtin_function().into(),
303            arguments: vec![Expression::ElementReference(Rc::downgrade(element))],
304            source_location,
305        })
306    } else {
307        None
308    }
309}