i_slint_compiler/passes/
lower_states.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Pass that create a state property, and change all the binding to depend on that property
5
6use 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    // Maps State name string -> integer id
54    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        // We check earlier that the property is in the set of changed properties, so a binding bust have been assigned
173        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
188/// Returns a suitable unique name for the "state" property
189fn 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    /// Workaround: the expression can't be represented with the current data structure, so make it an error for now.
203    InvalidBecauseOfIssue1461,
204}
205
206/// Return the expression binding currently associated to the given property
207fn 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                    // Check that the expression is valid in the new scope
220                    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}