mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-03 10:23:32 +00:00
209 lines
7.6 KiB
Rust
209 lines
7.6 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
//! Pass that create a state property, and change all the binding to depend on that property
|
|
|
|
use crate::diagnostics::BuildDiagnostics;
|
|
use crate::diagnostics::SourceLocation;
|
|
use crate::expression_tree::*;
|
|
use crate::langtype::Type;
|
|
use crate::object_tree::*;
|
|
use std::cell::RefCell;
|
|
use std::collections::HashSet;
|
|
use std::{collections::HashMap, rc::Rc};
|
|
|
|
pub fn lower_states(
|
|
component: &Rc<Component>,
|
|
tr: &crate::typeregister::TypeRegister,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let state_info_type = tr.lookup("StateInfo");
|
|
assert!(matches!(state_info_type, Type::Struct { name: Some(_), .. }));
|
|
recurse_elem(&component.root_element, &(), &mut |elem, _| {
|
|
lower_state_in_element(elem, &state_info_type, diag)
|
|
});
|
|
}
|
|
|
|
fn lower_state_in_element(
|
|
root_element: &ElementRc,
|
|
state_info_type: &Type,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
if root_element.borrow().states.is_empty() {
|
|
return;
|
|
}
|
|
let has_transitions = !root_element.borrow().transitions.is_empty();
|
|
let state_property_name = compute_state_property_name(root_element);
|
|
let state_property =
|
|
Expression::PropertyReference(NamedReference::new(root_element, &state_property_name));
|
|
let state_property_ref = if has_transitions {
|
|
Expression::StructFieldAccess {
|
|
base: Box::new(state_property.clone()),
|
|
name: "current-state".into(),
|
|
}
|
|
} else {
|
|
state_property.clone()
|
|
};
|
|
let mut affected_properties = HashSet::new();
|
|
// Maps State name string -> integer id
|
|
let mut states_id = HashMap::new();
|
|
let mut state_value = Expression::NumberLiteral(0., Unit::None);
|
|
let states = std::mem::take(&mut root_element.borrow_mut().states);
|
|
for (idx, state) in states.into_iter().enumerate().rev() {
|
|
if let Some(condition) = &state.condition {
|
|
state_value = Expression::Condition {
|
|
condition: Box::new(condition.clone()),
|
|
true_expr: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
|
|
false_expr: Box::new(std::mem::take(&mut state_value)),
|
|
};
|
|
}
|
|
for (ne, expr, node) in state.property_changes {
|
|
affected_properties.insert(ne.clone());
|
|
let e = ne.element();
|
|
let property_expr = match expression_for_property(&e, ne.name()) {
|
|
ExpressionForProperty::TwoWayBinding => {
|
|
diag.push_error(
|
|
format!("Cannot change the property '{}' in a state because it is initialized with a two-way binding", ne.name()),
|
|
&node
|
|
);
|
|
continue;
|
|
}
|
|
ExpressionForProperty::Expression(e) => e,
|
|
};
|
|
let new_expr = Expression::Condition {
|
|
condition: Box::new(Expression::BinaryExpression {
|
|
lhs: Box::new(state_property_ref.clone()),
|
|
rhs: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
|
|
op: '=',
|
|
}),
|
|
true_expr: Box::new(expr),
|
|
false_expr: Box::new(property_expr),
|
|
};
|
|
match e.borrow_mut().bindings.entry(ne.name().to_owned()) {
|
|
std::collections::btree_map::Entry::Occupied(mut e) => {
|
|
e.get_mut().get_mut().expression = new_expr
|
|
}
|
|
std::collections::btree_map::Entry::Vacant(e) => {
|
|
e.insert(RefCell::new(new_expr.into()));
|
|
}
|
|
};
|
|
}
|
|
states_id.insert(state.id, idx as i32 + 1);
|
|
}
|
|
|
|
root_element.borrow_mut().property_declarations.insert(
|
|
state_property_name.clone(),
|
|
PropertyDeclaration {
|
|
property_type: if has_transitions { state_info_type.clone() } else { Type::Int32 },
|
|
..PropertyDeclaration::default()
|
|
},
|
|
);
|
|
root_element
|
|
.borrow_mut()
|
|
.bindings
|
|
.insert(state_property_name, RefCell::new(state_value.into()));
|
|
|
|
lower_transitions_in_element(
|
|
root_element,
|
|
state_property,
|
|
states_id,
|
|
affected_properties,
|
|
diag,
|
|
);
|
|
}
|
|
|
|
fn lower_transitions_in_element(
|
|
elem: &ElementRc,
|
|
state_property: Expression,
|
|
states_id: HashMap<String, i32>,
|
|
affected_properties: HashSet<NamedReference>,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let transitions = std::mem::take(&mut elem.borrow_mut().transitions);
|
|
let mut props =
|
|
HashMap::<NamedReference, (SourceLocation, Vec<TransitionPropertyAnimation>)>::new();
|
|
for transition in transitions {
|
|
let state = states_id.get(&transition.state_id).unwrap_or_else(|| {
|
|
diag.push_error(
|
|
format!("State '{}' does not exist", transition.state_id),
|
|
&transition.node,
|
|
);
|
|
&0
|
|
});
|
|
|
|
for (p, span, animation) in transition.property_animations {
|
|
if !affected_properties.contains(&p) {
|
|
diag.push_error(
|
|
"The property is not changed as part of this transition".into(),
|
|
&span,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let t = TransitionPropertyAnimation {
|
|
state_id: *state,
|
|
is_out: transition.is_out,
|
|
animation,
|
|
};
|
|
props.entry(p).or_insert_with(|| (span.clone(), vec![])).1.push(t);
|
|
}
|
|
}
|
|
for (ne, (span, animations)) in props {
|
|
let e = ne.element();
|
|
// We check earlier that the property is in the set of changed properties, so a binding bust have been assigned
|
|
let old_anim = e.borrow().bindings.get(ne.name()).unwrap().borrow_mut().animation.replace(
|
|
PropertyAnimation::Transition { state_ref: state_property.clone(), animations },
|
|
);
|
|
if old_anim.is_some() {
|
|
diag.push_error(
|
|
format!(
|
|
"The property '{}' cannot have transition because it already has an animation",
|
|
ne.name()
|
|
),
|
|
&span,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a suitable unique name for the "state" property
|
|
fn compute_state_property_name(root_element: &ElementRc) -> String {
|
|
let mut property_name = "state".to_owned();
|
|
while root_element.borrow().lookup_property(property_name.as_ref()).property_type
|
|
!= Type::Invalid
|
|
{
|
|
property_name += "-";
|
|
}
|
|
property_name
|
|
}
|
|
|
|
enum ExpressionForProperty {
|
|
TwoWayBinding,
|
|
Expression(Expression),
|
|
}
|
|
|
|
/// Return the expression binding currently associated to the given property
|
|
fn expression_for_property(element: &ElementRc, name: &str) -> ExpressionForProperty {
|
|
let mut element_it = Some(element.clone());
|
|
while let Some(element) = element_it {
|
|
if let Some(e) = element.borrow().bindings.get(name) {
|
|
let e = e.borrow();
|
|
if !e.two_way_bindings.is_empty() {
|
|
return ExpressionForProperty::TwoWayBinding;
|
|
}
|
|
if !matches!(e.expression, Expression::Invalid) {
|
|
return ExpressionForProperty::Expression(e.expression.clone());
|
|
}
|
|
}
|
|
element_it = if let Type::Component(base) = &element.borrow().base_type {
|
|
Some(base.root_element.clone())
|
|
} else {
|
|
None
|
|
};
|
|
}
|
|
let expr = super::materialize_fake_properties::initialize(element, name).unwrap_or_else(|| {
|
|
Expression::default_value_for_type(&element.borrow().lookup_property(name).property_type)
|
|
});
|
|
|
|
ExpressionForProperty::Expression(expr)
|
|
}
|