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