mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-03 10:23:32 +00:00
226 lines
8.4 KiB
Rust
226 lines
8.4 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
//! This pass removes the property used in a two ways bindings
|
|
|
|
use crate::diagnostics::BuildDiagnostics;
|
|
use crate::expression_tree::{BindingExpression, NamedReference};
|
|
use crate::object_tree::*;
|
|
use std::cell::RefCell;
|
|
use std::collections::{btree_map::Entry, HashMap, HashSet};
|
|
use std::rc::Rc;
|
|
|
|
type Mapping = HashMap<NamedReference, NamedReference>;
|
|
|
|
#[derive(Default, Debug)]
|
|
struct PropertySets {
|
|
map: HashMap<NamedReference, Rc<RefCell<HashSet<NamedReference>>>>,
|
|
all_sets: Vec<Rc<RefCell<HashSet<NamedReference>>>>,
|
|
}
|
|
|
|
impl PropertySets {
|
|
fn add_link(&mut self, p1: NamedReference, p2: NamedReference) {
|
|
if !std::rc::Weak::ptr_eq(
|
|
&p1.element().borrow().enclosing_component,
|
|
&p2.element().borrow().enclosing_component,
|
|
) && (p1.element().borrow().enclosing_component.upgrade().unwrap().is_global()
|
|
== p2.element().borrow().enclosing_component.upgrade().unwrap().is_global())
|
|
{
|
|
// We can only merge aliases if they are in the same Component. (unless one of them is global)
|
|
// TODO: actually we could still merge two alias in a component pointing to the same
|
|
// property in a parent component
|
|
return;
|
|
}
|
|
if let Some(s1) = self.map.get(&p1).cloned() {
|
|
if let Some(s2) = self.map.get(&p2).cloned() {
|
|
if Rc::ptr_eq(&s1, &s2) {
|
|
return;
|
|
}
|
|
for x in s1.borrow().iter() {
|
|
self.map.insert(x.clone(), s2.clone());
|
|
s2.borrow_mut().insert(x.clone());
|
|
}
|
|
*s1.borrow_mut() = HashSet::new();
|
|
} else {
|
|
s1.borrow_mut().insert(p2.clone());
|
|
self.map.insert(p2, s1);
|
|
}
|
|
} else if let Some(s2) = self.map.get(&p2).cloned() {
|
|
s2.borrow_mut().insert(p1.clone());
|
|
self.map.insert(p1, s2);
|
|
} else {
|
|
let mut set = HashSet::new();
|
|
set.insert(p1.clone());
|
|
set.insert(p2.clone());
|
|
let set = Rc::new(RefCell::new(set));
|
|
self.map.insert(p1, set.clone());
|
|
self.map.insert(p2, set.clone());
|
|
self.all_sets.push(set)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn remove_aliases(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
|
|
// collect all sets that are linked together
|
|
let mut property_sets = PropertySets::default();
|
|
|
|
let mut process_element = |e: &ElementRc| {
|
|
'bindings: for (name, binding) in &e.borrow().bindings {
|
|
for nr in &binding.borrow().two_way_bindings {
|
|
let other_e = nr.element();
|
|
if name == nr.name() && Rc::ptr_eq(e, &other_e) {
|
|
diag.push_error("Property cannot alias to itself".into(), &*binding.borrow());
|
|
continue 'bindings;
|
|
}
|
|
property_sets.add_link(NamedReference::new(e, name), nr.clone());
|
|
}
|
|
}
|
|
};
|
|
|
|
recurse_elem_including_sub_components(component, &(), &mut |e, &()| process_element(e));
|
|
for g in &component.used_types.borrow().globals {
|
|
process_element(&g.root_element)
|
|
}
|
|
|
|
// The key will be removed and replaced by the named reference
|
|
let mut aliases_to_remove = Mapping::new();
|
|
|
|
// For each set, find a "master" property. Only reference to this master property will be kept,
|
|
// and only the master property will keep its binding
|
|
for set in property_sets.all_sets {
|
|
let set = set.borrow();
|
|
let mut set_iter = set.iter();
|
|
if let Some(mut best) = set_iter.next().cloned() {
|
|
for candidate in set_iter {
|
|
best = best_property(component, best.clone(), candidate.clone());
|
|
}
|
|
for x in set.iter() {
|
|
if *x != best {
|
|
aliases_to_remove.insert(x.clone(), best.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do the replacements
|
|
visit_all_named_references(component, &mut |nr: &mut NamedReference| {
|
|
if let Some(new) = aliases_to_remove.get(nr) {
|
|
*nr = new.clone();
|
|
}
|
|
});
|
|
|
|
// Remove the properties
|
|
for (remove, to) in aliases_to_remove {
|
|
let elem = remove.element();
|
|
|
|
// adjust the bindings
|
|
let old_binding = elem.borrow_mut().bindings.remove(remove.name());
|
|
let must_simplify = if let Some(mut binding) = old_binding.map(RefCell::into_inner) {
|
|
remove_from_binding_expression(&mut binding, &to);
|
|
if binding.has_binding() {
|
|
let to_elem = to.element();
|
|
match to_elem.borrow_mut().bindings.entry(to.name().to_owned()) {
|
|
Entry::Occupied(mut e) => {
|
|
let b = e.get_mut().get_mut();
|
|
remove_from_binding_expression(b, &to);
|
|
if b.priority < binding.priority || !b.has_binding() {
|
|
b.merge_with(&binding);
|
|
} else {
|
|
binding.merge_with(b);
|
|
*b = binding;
|
|
}
|
|
}
|
|
Entry::Vacant(e) => {
|
|
e.insert(binding.into());
|
|
}
|
|
};
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
} else {
|
|
true
|
|
};
|
|
|
|
if must_simplify {
|
|
let to_elem = to.element();
|
|
let mut to_elem = to_elem.borrow_mut();
|
|
if let Some(b) = to_elem.bindings.get_mut(to.name()) {
|
|
remove_from_binding_expression(b.get_mut(), &to);
|
|
if !b.get_mut().has_binding() {
|
|
to_elem.bindings.remove(to.name());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the declaration
|
|
{
|
|
let mut elem = elem.borrow_mut();
|
|
let used_externally = elem
|
|
.property_analysis
|
|
.borrow()
|
|
.get(remove.name())
|
|
.map_or(false, |v| v.is_read_externally || v.is_set_externally);
|
|
if let Some(d) = elem.property_declarations.get_mut(remove.name()) {
|
|
if d.expose_in_public_api || used_externally {
|
|
d.is_alias = Some(to.clone());
|
|
drop(elem);
|
|
// one must mark the aliased property as settable from outside
|
|
to.mark_as_set();
|
|
} else {
|
|
elem.property_declarations.remove(remove.name());
|
|
let analysis = elem.property_analysis.borrow().get(remove.name()).cloned();
|
|
if let Some(analysis) = analysis {
|
|
drop(elem);
|
|
to.element()
|
|
.borrow()
|
|
.property_analysis
|
|
.borrow_mut()
|
|
.entry(to.name().to_owned())
|
|
.or_default()
|
|
.merge(&analysis);
|
|
};
|
|
}
|
|
} else {
|
|
// This is not a declaration, we must re-create the binding
|
|
elem.bindings
|
|
.insert(remove.name().to_owned(), BindingExpression::new_two_way(to).into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_declaration(x: &NamedReference) -> bool {
|
|
x.element().borrow().property_declarations.contains_key(x.name())
|
|
}
|
|
|
|
/// Out of two named reference, return the one which is the best to keep.
|
|
fn best_property(
|
|
component: &Rc<Component>,
|
|
p1: NamedReference,
|
|
p2: NamedReference,
|
|
) -> NamedReference {
|
|
// Try to find which is the more canonical property
|
|
macro_rules! canonical_order {
|
|
($x: expr) => {{
|
|
(
|
|
!$x.element().borrow().enclosing_component.upgrade().unwrap().is_global(),
|
|
is_declaration(&$x),
|
|
!Rc::ptr_eq(&component.root_element, &$x.element()),
|
|
$x.element().borrow().id.clone(),
|
|
$x.name(),
|
|
)
|
|
}};
|
|
}
|
|
|
|
if canonical_order!(p1) < canonical_order!(p2) {
|
|
p1
|
|
} else {
|
|
p2
|
|
}
|
|
}
|
|
|
|
/// Remove the `to` from the two_way_bindings
|
|
fn remove_from_binding_expression(expression: &mut BindingExpression, to: &NamedReference) {
|
|
expression.two_way_bindings.retain(|x| x != to);
|
|
}
|