slint/sixtyfps_compiler/passes/focus_item.rs
Olivier Goffart fff6f04be0 Progressively work on pass so they work without inlining
Revert part of the previous commit that tries to do all the pass without inlining

Fixup the few first passes so they work without inlining, but still do a full
inlining for most passes

The goal is to make it work pass by pass until we can have everything without
requiring full inlining
2021-10-28 15:52:29 +02:00

137 lines
5.4 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
//! This pass follows the forward-focus property on the root element to determine the initial focus item
// as well as handle the forward for `focus()` calls in code.
use std::rc::Rc;
use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::expression_tree::{BindingExpression, BuiltinFunction, Expression};
use crate::langtype::Type;
use crate::object_tree::*;
enum FocusCheckResult {
ElementIsFocusable,
FocusForwarded(ElementRc, SourceLocation),
ElementIsNotFocusable,
}
pub fn get_explicit_forward_focus(
element: &ElementRc,
) -> Option<std::cell::Ref<BindingExpression>> {
let element = element.borrow();
if element.bindings.contains_key("forward-focus") {
Some(std::cell::Ref::map(element, |elem| &elem.bindings["forward-focus"]))
} else {
None
}
}
fn element_focus_check(element: &ElementRc) -> FocusCheckResult {
if let Some(forwarded_focus_binding) = get_explicit_forward_focus(element) {
if let Expression::ElementReference(target) = &forwarded_focus_binding.expression {
return FocusCheckResult::FocusForwarded(
target.upgrade().unwrap(),
forwarded_focus_binding.to_source_location(),
);
} else {
panic!("internal error: forward-focus property is of type ElementReference but received non-element-reference binding");
}
}
if matches!(&element.borrow().base_type.clone(), Type::Builtin(b) if b.accepts_focus) {
return FocusCheckResult::ElementIsFocusable;
}
FocusCheckResult::ElementIsNotFocusable
}
fn find_focusable_element(
mut element: ElementRc,
diag: &mut BuildDiagnostics,
) -> Option<ElementRc> {
let mut last_focus_forward_location = None;
loop {
match element_focus_check(&element) {
FocusCheckResult::ElementIsFocusable => break Some(element),
FocusCheckResult::FocusForwarded(forwarded_element, location) => {
element = forwarded_element;
last_focus_forward_location = Some(location);
}
FocusCheckResult::ElementIsNotFocusable => {
if let Some(location) = last_focus_forward_location {
diag.push_error("element is not focusable".into(), &location);
}
break None;
}
}
}
}
/// Ensure that all element references in SetFocusItem calls point to elements that can accept the focus, following
/// any `forward-focus` chains if needed.
fn resolve_element_reference_in_set_focus_call(expr: &mut Expression, diag: &mut BuildDiagnostics) {
if let Expression::FunctionCall { function, arguments, source_location } = expr {
if let Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem, _) =
function.as_ref()
{
if arguments.len() != 1 {
panic!("internal compiler error: Invalid argument generated for SetFocusItem call");
}
if let Expression::ElementReference(weak_focus_target) = &mut arguments[0] {
let focus_target = weak_focus_target.upgrade().expect(
"internal compiler error: weak SetFocusItem parameter cannot be dangling",
);
match find_focusable_element(focus_target, diag) {
Some(new_focus_target) => {
*weak_focus_target = Rc::downgrade(&new_focus_target);
}
None => diag.push_error(
"focus() can only be called on focusable elements".into(),
source_location,
),
}
return;
}
}
}
expr.visit_mut(|e| resolve_element_reference_in_set_focus_call(e, diag))
}
pub fn resolve_element_reference_in_set_focus_calls(
component: &Rc<Component>,
diag: &mut BuildDiagnostics,
) {
visit_all_expressions(component, |e, _| resolve_element_reference_in_set_focus_call(e, diag));
}
/// Generate setup code to pass window focus to the root item or a forwarded focus if applicable.
pub fn determine_initial_focus_item(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
if let Some(root_focus_item) = find_focusable_element(component.root_element.clone(), diag) {
let setup_code = Expression::FunctionCall {
function: Box::new(Expression::BuiltinFunctionReference(
BuiltinFunction::SetFocusItem,
None,
)),
arguments: vec![Expression::ElementReference(Rc::downgrade(&root_focus_item))],
source_location: None,
};
component.setup_code.borrow_mut().push(setup_code);
}
}
/// The `forward-focus` property is not a real property that can be generated, so remove any bindings to it
/// to avoid them being materialized.
pub fn erase_forward_focus_properties(component: &Rc<Component>) {
recurse_elem_no_borrow(&component.root_element, &(), &mut |elem, _| {
elem.borrow_mut().bindings.remove("forward-focus");
})
}