i_slint_compiler/passes/
lower_states.rs1use crate::diagnostics::BuildDiagnostics;
7use crate::diagnostics::SourceLocation;
8use crate::diagnostics::Spanned;
9use crate::expression_tree::*;
10use crate::langtype::ElementType;
11use crate::langtype::Type;
12use crate::object_tree::*;
13use smol_str::SmolStr;
14use std::cell::RefCell;
15use std::collections::{HashMap, HashSet};
16use std::rc::{Rc, Weak};
17
18pub fn lower_states(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
19 let state_info_type = crate::typeregister::BUILTIN.with(|b| b.state_info_type.clone().into());
20 recurse_elem(&component.root_element, &(), &mut |elem, _| {
21 lower_state_in_element(elem, &state_info_type, diag)
22 });
23}
24
25fn lower_state_in_element(
26 root_element: &ElementRc,
27 state_info_type: &Type,
28 diag: &mut BuildDiagnostics,
29) {
30 if root_element.borrow().states.is_empty() {
31 return;
32 }
33 let has_transitions = !root_element.borrow().transitions.is_empty();
34 let state_property_name = compute_state_property_name(root_element);
35 let state_property = Expression::PropertyReference(NamedReference::new(
36 root_element,
37 state_property_name.clone(),
38 ));
39 let state_property_ref = if has_transitions {
40 Expression::StructFieldAccess {
41 base: Box::new(state_property.clone()),
42 name: "current-state".into(),
43 }
44 } else {
45 state_property.clone()
46 };
47 let mut affected_properties = HashSet::new();
48 let mut states_id = HashMap::new();
50 let mut state_value = Expression::NumberLiteral(0., Unit::None);
51 let states = std::mem::take(&mut root_element.borrow_mut().states);
52 for (idx, state) in states.into_iter().enumerate().rev() {
53 if let Some(condition) = &state.condition {
54 state_value = Expression::Condition {
55 condition: Box::new(condition.clone()),
56 true_expr: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
57 false_expr: Box::new(std::mem::take(&mut state_value)),
58 };
59 }
60 for (ne, expr, node) in state.property_changes {
61 affected_properties.insert(ne.clone());
62 let e = ne.element();
63 let property_expr = match expression_for_property(&e, ne.name()) {
64 ExpressionForProperty::TwoWayBinding => {
65 diag.push_error(
66 format!("Cannot change the property '{}' in a state because it is initialized with a two-way binding", ne.name()),
67 &node
68 );
69 continue;
70 }
71 ExpressionForProperty::Expression(e) => e,
72 ExpressionForProperty::InvalidBecauseOfIssue1461 => {
73 diag.push_error(
74 format!("Internal error: The expression for the default state currently cannot be represented: https://github.com/slint-ui/slint/issues/1461\nAs a workaround, add a binding for property {}", ne.name()),
75 &node
76 );
77 continue;
78 }
79 };
80 let new_expr = Expression::Condition {
81 condition: Box::new(Expression::BinaryExpression {
82 lhs: Box::new(state_property_ref.clone()),
83 rhs: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
84 op: '=',
85 }),
86 true_expr: Box::new(expr),
87 false_expr: Box::new(property_expr),
88 };
89 match e.borrow_mut().bindings.entry(ne.name().clone()) {
90 std::collections::btree_map::Entry::Occupied(mut e) => {
91 e.get_mut().get_mut().expression = new_expr
92 }
93 std::collections::btree_map::Entry::Vacant(e) => {
94 let mut r = BindingExpression::from(new_expr);
95 r.priority = 1;
96 e.insert(r.into());
97 }
98 };
99 }
100 states_id.insert(state.id, idx as i32 + 1);
101 }
102
103 root_element.borrow_mut().property_declarations.insert(
104 state_property_name.clone(),
105 PropertyDeclaration {
106 property_type: if has_transitions { state_info_type.clone() } else { Type::Int32 },
107 ..PropertyDeclaration::default()
108 },
109 );
110 root_element
111 .borrow_mut()
112 .bindings
113 .insert(state_property_name, RefCell::new(state_value.into()));
114
115 lower_transitions_in_element(
116 root_element,
117 state_property,
118 states_id,
119 affected_properties,
120 diag,
121 );
122}
123
124fn lower_transitions_in_element(
125 elem: &ElementRc,
126 state_property: Expression,
127 states_id: HashMap<SmolStr, i32>,
128 affected_properties: HashSet<NamedReference>,
129 diag: &mut BuildDiagnostics,
130) {
131 let transitions = std::mem::take(&mut elem.borrow_mut().transitions);
132 let mut props =
133 HashMap::<NamedReference, (SourceLocation, Vec<TransitionPropertyAnimation>)>::new();
134 for transition in transitions {
135 let state = states_id.get(&transition.state_id).unwrap_or_else(|| {
136 diag.push_error(
137 format!("State '{}' does not exist", transition.state_id),
138 transition
139 .node
140 .DeclaredIdentifier()
141 .as_ref()
142 .map(|x| x as &dyn Spanned)
143 .unwrap_or(&transition.node as &dyn Spanned),
144 );
145 &0
146 });
147
148 for (p, span, animation) in transition.property_animations {
149 if !affected_properties.contains(&p) {
150 diag.push_error(
151 "The property is not changed as part of this transition".into(),
152 &span,
153 );
154 continue;
155 }
156
157 let t = TransitionPropertyAnimation {
158 state_id: *state,
159 direction: transition.direction,
160 animation,
161 };
162 props.entry(p).or_insert_with(|| (span.clone(), Vec::new())).1.push(t);
163 }
164 }
165 for (ne, (span, animations)) in props {
166 let e = ne.element();
167 let old_anim = e.borrow().bindings.get(ne.name()).unwrap().borrow_mut().animation.replace(
169 PropertyAnimation::Transition { state_ref: state_property.clone(), animations },
170 );
171 if old_anim.is_some() {
172 diag.push_error(
173 format!(
174 "The property '{}' cannot have transition because it already has an animation",
175 ne.name()
176 ),
177 &span,
178 );
179 }
180 }
181}
182
183fn compute_state_property_name(root_element: &ElementRc) -> SmolStr {
185 let mut property_name = "state".to_owned();
186 while root_element.borrow().lookup_property(property_name.as_ref()).property_type
187 != Type::Invalid
188 {
189 property_name += "-";
190 }
191 property_name.into()
192}
193
194enum ExpressionForProperty {
195 TwoWayBinding,
196 Expression(Expression),
197 InvalidBecauseOfIssue1461,
199}
200
201fn expression_for_property(element: &ElementRc, name: &str) -> ExpressionForProperty {
203 let mut element_it = Some(element.clone());
204 let mut in_base = false;
205 while let Some(elem) = element_it {
206 if let Some(e) = elem.borrow().bindings.get(name) {
207 let e = e.borrow();
208 if !e.two_way_bindings.is_empty() {
209 return ExpressionForProperty::TwoWayBinding;
210 }
211 let mut expr = e.expression.clone();
212 if !matches!(expr, Expression::Invalid) {
213 if in_base {
214 let mut has_invalid = false;
216 expr.visit_recursive_mut(&mut |ex| match ex {
217 Expression::PropertyReference(nr)
218 | Expression::FunctionCall {
219 function: Callable::Callback(nr) | Callable::Function(nr),
220 ..
221 } => {
222 let e = nr.element();
223 if Rc::ptr_eq(&e, &elem) {
224 *nr = NamedReference::new(element, nr.name().clone());
225 } else if Weak::ptr_eq(
226 &e.borrow().enclosing_component,
227 &elem.borrow().enclosing_component,
228 ) {
229 has_invalid = true;
230 }
231 }
232 _ => (),
233 });
234 if has_invalid {
235 return ExpressionForProperty::InvalidBecauseOfIssue1461;
236 }
237 }
238
239 return ExpressionForProperty::Expression(expr);
240 }
241 }
242 element_it = if let ElementType::Component(base) = &elem.borrow().base_type {
243 in_base = true;
244 Some(base.root_element.clone())
245 } else {
246 None
247 };
248 }
249 let expr = super::materialize_fake_properties::initialize(element, name).unwrap_or_else(|| {
250 Expression::default_value_for_type(&element.borrow().lookup_property(name).property_type)
251 });
252
253 ExpressionForProperty::Expression(expr)
254}