1use crate::diagnostics::BuildDiagnostics;
7use crate::diagnostics::SourceLocation;
8use crate::diagnostics::Spanned;
9use crate::expression_tree::*;
10use crate::langtype::{PropertyLookupMode, Type};
11use crate::object_tree::forward_inherited_expression::{
12 ForwardedReferenceCache, InheritedExpression, forward_inherited_expression,
13};
14use crate::object_tree::*;
15use crate::symbol_counters::SymbolCounters;
16use smol_str::SmolStr;
17use std::collections::{HashMap, HashSet};
18use std::rc::Rc;
19
20pub fn lower_states(
21 component: &Rc<Component>,
22 symbol_counters: &SymbolCounters,
23 forwarded_references: &mut ForwardedReferenceCache,
24 diag: &mut BuildDiagnostics,
25) {
26 let state_info_type = crate::typeregister::BUILTIN.state_info_type.clone().into();
27 recurse_elem(&component.root_element, &(), &mut |elem, _| {
28 lower_state_in_element(elem, &state_info_type, symbol_counters, forwarded_references, diag)
29 });
30}
31
32fn lower_state_in_element(
33 root_element: &ElementRc,
34 state_info_type: &Type,
35 symbol_counters: &SymbolCounters,
36 forwarded_references: &mut ForwardedReferenceCache,
37 diag: &mut BuildDiagnostics,
38) {
39 if root_element.borrow().states.is_empty() {
40 return;
41 }
42 let has_transitions = !root_element.borrow().transitions.is_empty();
43 let state_property_nr = crate::layout::create_new_prop(
44 root_element,
45 SmolStr::new_static("state"),
46 if has_transitions { state_info_type.clone() } else { Type::Int32 },
47 );
48 let state_property = Expression::PropertyReference(state_property_nr.clone());
49 let state_property_ref = if has_transitions {
50 Expression::StructFieldAccess {
51 base: Box::new(state_property.clone()),
52 name: "current-state".into(),
53 }
54 } else {
55 state_property.clone()
56 };
57 let mut affected_properties = HashSet::new();
58 let mut states_id = HashMap::new();
60 let mut state_value = Expression::NumberLiteral(0., Unit::None);
61 let states = std::mem::take(&mut root_element.borrow_mut().states);
62 for (idx, state) in states.into_iter().enumerate().rev() {
63 if let Some(condition) = &state.condition {
64 state_value = Expression::Condition {
65 condition: Box::new(condition.clone()),
66 true_expr: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
67 false_expr: Box::new(std::mem::take(&mut state_value)),
68 source_location: state.selection.clone(),
69 };
70 }
71 for (property_reference, expr, node) in state.property_changes {
72 affected_properties.insert(property_reference.clone());
73 let element = property_reference.element();
74 let property_expr = match expression_for_property(
75 &element,
76 property_reference.name(),
77 symbol_counters,
78 forwarded_references,
79 ) {
80 ExpressionForProperty::TwoWayBinding => {
81 diag.push_error(
82 format!("Cannot change the property '{}' in a state because it is initialized with a two-way binding", property_reference.name()),
83 &node
84 );
85 continue;
86 }
87 ExpressionForProperty::Expression(e) => e,
88 };
89 let new_expr = Expression::Condition {
90 condition: Box::new(Expression::BinaryExpression {
91 source_location: None,
92 lhs: Box::new(state_property_ref.clone()),
93 rhs: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
94 op: '=',
95 }),
96 true_expr: Box::new(expr),
97 false_expr: Box::new(property_expr),
98 source_location: Some(ConditionLocation::StateChange(
99 node.QualifiedName().to_source_location(),
100 )),
101 };
102
103 let name = property_reference.name();
104 if let Some(cell) = element.borrow().binding_cell_including_synthetic(name) {
105 cell.borrow_mut().set_value_expression(new_expr);
108 } else {
109 let mut r = BindingExpression::from(new_expr);
110 r.priority = 1;
111 element.borrow_mut().set_binding(name.clone(), r);
112 }
113 }
114 states_id.insert(state.id, idx as i32 + 1);
115 }
116
117 root_element.borrow_mut().set_binding(state_property_nr.name().clone(), state_value.into());
118
119 lower_transitions_in_element(
120 root_element,
121 state_property,
122 states_id,
123 affected_properties,
124 diag,
125 );
126}
127
128fn lower_transitions_in_element(
129 elem: &ElementRc,
130 state_property: Expression,
131 states_id: HashMap<SmolStr, i32>,
132 affected_properties: HashSet<NamedReference>,
133 diag: &mut BuildDiagnostics,
134) {
135 let transitions = std::mem::take(&mut elem.borrow_mut().transitions);
136 let mut props =
137 HashMap::<NamedReference, (SourceLocation, Vec<TransitionPropertyAnimation>)>::new();
138 for transition in transitions {
139 let state = states_id.get(&transition.state_id).unwrap_or_else(|| {
140 diag.push_error(
141 format!("State '{}' does not exist", transition.state_id),
142 transition
143 .node
144 .DeclaredIdentifier()
145 .as_ref()
146 .map(|x| x as &dyn Spanned)
147 .unwrap_or(&transition.node as &dyn Spanned),
148 );
149 &0
150 });
151
152 for (p, span, animation) in transition.property_animations {
153 if !affected_properties.contains(&p) {
154 diag.push_error(
155 "The property is not changed as part of this transition".into(),
156 &span,
157 );
158 continue;
159 }
160
161 let t = TransitionPropertyAnimation {
162 state_id: *state,
163 direction: transition.direction,
164 animation,
165 };
166 props.entry(p).or_insert_with(|| (span.clone(), Vec::new())).1.push(t);
167 }
168 }
169 for (ne, (span, animations)) in props {
170 let e = ne.element();
171 let old_anim = e.borrow().binding_mut(ne.name()).unwrap().animation.replace(
173 PropertyAnimation::Transition { state_ref: state_property.clone(), animations },
174 );
175 if old_anim.is_some() {
176 diag.push_error(
177 format!(
178 "The property '{}' cannot have transition because it already has an animation",
179 ne.name()
180 ),
181 &span,
182 );
183 }
184 }
185}
186
187enum ExpressionForProperty {
188 TwoWayBinding,
189 Expression(Expression),
190}
191
192fn expression_for_property(
194 element: &ElementRc,
195 name: &str,
196 symbol_counters: &SymbolCounters,
197 forwarded_references: &mut ForwardedReferenceCache,
198) -> ExpressionForProperty {
199 let local_binding = element
200 .borrow()
201 .binding(name)
202 .map(|binding| (!binding.two_way_bindings.is_empty(), binding.expression.clone()));
203 if let Some((is_two_way_binding, expression)) = local_binding {
204 if is_two_way_binding {
205 return ExpressionForProperty::TwoWayBinding;
206 }
207 if !matches!(expression, Expression::Invalid) {
208 return ExpressionForProperty::Expression(expression);
209 }
210 }
211
212 match forward_inherited_expression(element, name, symbol_counters, forwarded_references) {
213 InheritedExpression::Expression(expression) => {
214 return ExpressionForProperty::Expression(expression);
215 }
216 InheritedExpression::TwoWayBinding => return ExpressionForProperty::TwoWayBinding,
217 InheritedExpression::Unbound => {}
218 }
219
220 let expression =
221 super::materialize_fake_properties::initialize(element, name).unwrap_or_else(|| {
222 Expression::default_value_for_type(
223 &element
224 .borrow()
225 .lookup_property(name, PropertyLookupMode::InternalName)
226 .property_type,
227 )
228 });
229
230 ExpressionForProperty::Expression(expression)
231}