slint/internal/compiler/passes/move_declarations.rs
Olivier Goffart 28ae8f7bc4 Refactoring: split ElementType away from the types used as property type
These are two different concept, and it is confusing to keep them in the
same enum

We want to support component without any base element, and Void is
already used for global component, so do this refactoring before
2022-10-26 14:50:44 +02:00

181 lines
7.1 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! This pass moves all declaration of properties or callback to the root
use crate::expression_tree::NamedReference;
use crate::object_tree::*;
use crate::langtype::ElementType;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::rc::Rc;
struct Declarations {
property_declarations: BTreeMap<String, PropertyDeclaration>,
}
impl Declarations {
fn take_from_element(e: &mut Element) -> Self {
Declarations { property_declarations: core::mem::take(&mut e.property_declarations) }
}
}
pub fn move_declarations(component: &Rc<Component>) {
simplify_optimized_items_recursive(component);
do_move_declarations(component);
}
fn do_move_declarations(component: &Rc<Component>) {
let mut decl = Declarations::take_from_element(&mut *component.root_element.borrow_mut());
component.popup_windows.borrow().iter().for_each(|f| do_move_declarations(&f.component));
let mut new_root_bindings = HashMap::new();
let mut new_root_property_analysis = HashMap::new();
let move_bindings_and_animations = &mut |elem: &ElementRc| {
visit_all_named_references_in_element(elem, fixup_reference);
if elem.borrow().repeated.is_some() {
if let ElementType::Component(base) = &elem.borrow().base_type {
do_move_declarations(base);
} else {
panic!("Repeated element should have a component as base because of the repeater_component.rs pass")
}
debug_assert!(
elem.borrow().property_declarations.is_empty() && elem.borrow().children.is_empty(),
"Repeated element should be empty because of the repeater_component.rs pass"
);
return;
}
// take the bindings so we do nt keep the borrow_mut of the element
let bindings = core::mem::take(&mut elem.borrow_mut().bindings);
let mut new_bindings = BindingsMap::default();
for (k, e) in bindings {
let will_be_moved = elem.borrow().property_declarations.contains_key(&k);
if will_be_moved {
new_root_bindings.insert(map_name(elem, k.as_str()), e);
} else {
new_bindings.insert(k, e);
}
}
elem.borrow_mut().bindings = new_bindings;
let property_analysis = elem.borrow().property_analysis.take();
let mut new_property_analysis = HashMap::with_capacity(property_analysis.len());
for (prop, a) in property_analysis {
let will_be_moved = elem.borrow().property_declarations.contains_key(&prop);
if will_be_moved {
new_root_property_analysis.insert(map_name(elem, prop.as_str()), a);
} else {
new_property_analysis.insert(prop, a);
}
}
*elem.borrow().property_analysis.borrow_mut() = new_property_analysis;
};
component.optimized_elements.borrow().iter().for_each(|e| move_bindings_and_animations(e));
recurse_elem(&component.root_element, &(), &mut |e, _| move_bindings_and_animations(e));
component.root_constraints.borrow_mut().visit_named_references(&mut fixup_reference);
component.popup_windows.borrow_mut().iter_mut().for_each(|p| {
fixup_reference(&mut p.x);
fixup_reference(&mut p.y);
visit_all_named_references(&p.component, &mut fixup_reference)
});
for pd in decl.property_declarations.values_mut() {
pd.is_alias.as_mut().map(fixup_reference);
}
let move_properties = &mut |elem: &ElementRc| {
let elem_decl = Declarations::take_from_element(&mut *elem.borrow_mut());
decl.property_declarations.extend(
elem_decl.property_declarations.into_iter().map(|(p, d)| (map_name(elem, &*p), d)),
);
};
recurse_elem(&component.root_element, &(), &mut |elem, _| move_properties(elem));
component.optimized_elements.borrow().iter().for_each(|e| move_properties(e));
{
let mut r = component.root_element.borrow_mut();
r.property_declarations = decl.property_declarations;
r.bindings.extend(new_root_bindings.into_iter());
r.property_analysis.borrow_mut().extend(new_root_property_analysis.into_iter());
}
// By now, the optimized item should be unused
#[cfg(debug_assertions)]
assert_optimized_item_unused(component.optimized_elements.borrow().as_slice());
core::mem::take(&mut *component.optimized_elements.borrow_mut());
}
fn fixup_reference(nr: &mut NamedReference) {
let e = nr.element();
let component = e.borrow().enclosing_component.upgrade().unwrap();
if !Rc::ptr_eq(&e, &component.root_element)
&& e.borrow().property_declarations.contains_key(nr.name())
{
*nr = NamedReference::new(&component.root_element, map_name(&e, nr.name()).as_str());
}
}
fn map_name(e: &ElementRc, s: &str) -> String {
format!("{}-{}", e.borrow().id, s)
}
fn simplify_optimized_items_recursive(component: &Rc<Component>) {
simplify_optimized_items(component.optimized_elements.borrow().as_slice());
component
.popup_windows
.borrow()
.iter()
.for_each(|f| simplify_optimized_items_recursive(&f.component));
recurse_elem(&component.root_element, &(), &mut |elem, _| {
if elem.borrow().repeated.is_some() {
if let ElementType::Component(base) = &elem.borrow().base_type {
simplify_optimized_items_recursive(base);
}
}
});
}
/// Optimized item are not used for the fact that they are items, but their properties
/// might still be used. So we must pretend all the properties are declared in the
/// item itself so the move_declaration pass can move the declaration in the component root
fn simplify_optimized_items(items: &[ElementRc]) {
for elem in items {
recurse_elem(elem, &(), &mut |elem, _| {
let base = core::mem::take(&mut elem.borrow_mut().base_type);
if let ElementType::Builtin(c) = base {
// This assume that all properties of builtin items are fine with the default value
elem.borrow_mut().property_declarations.extend(c.properties.iter().map(
|(k, v)| {
(
k.clone(),
PropertyDeclaration {
property_type: v.ty.clone(),
..Default::default()
},
)
},
));
} else {
unreachable!("Only builtin items should be optimized")
}
})
}
}
/// Check there are no longer references to optimized items
#[cfg(debug_assertions)]
fn assert_optimized_item_unused(items: &[ElementRc]) {
for e in items {
recurse_elem(e, &(), &mut |e, _| {
assert_eq!(Rc::strong_count(e), 1);
// no longer working because we have weak count in the named reference holder
//assert_eq!(Rc::weak_count(e), 0);
});
}
}