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