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