| 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 | use std::cell::RefCell; |
| 8 | use std::rc::Rc; |
| 9 | |
| 10 | use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned}; |
| 11 | use crate::expression_tree::{BuiltinFunction, Callable, Expression}; |
| 12 | use crate::langtype::{ElementType, Function, Type}; |
| 13 | use crate::namedreference::NamedReference; |
| 14 | use crate::object_tree::*; |
| 15 | use by_address::ByAddress; |
| 16 | use smol_str::SmolStr; |
| 17 | use std::collections::{HashMap, HashSet}; |
| 18 | use strum::IntoEnumIterator; |
| 19 | |
| 20 | /// Generate setup code to pass window focus to the root item or a forwarded focus if applicable. |
| 21 | pub fn call_focus_on_init(component: &Rc<Component>) { |
| 22 | if let Some(focus_call_code: Expression) = |
| 23 | call_set_focus_function(&component.root_element, source_location:None, function_type: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. |
| 31 | pub 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. |
| 68 | struct LocalFocusForwards<'a> { |
| 69 | forwards: |
| 70 | HashMap<ByAddress<Rc<RefCell<Element>>>, Option<(Rc<RefCell<Element>>, SourceLocation)>>, |
| 71 | diag: &'a mut BuildDiagnostics, |
| 72 | } |
| 73 | |
| 74 | impl<'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)] |
| 244 | enum FocusFunctionType { |
| 245 | SetFocus, |
| 246 | ClearFocus, |
| 247 | } |
| 248 | |
| 249 | impl 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 | |
| 265 | fn 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 | } |
| 306 | |