Skip to main content

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(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    // Maps State name string -> integer id
49    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        // We check earlier that the property is in the set of changed properties, so a binding bust have been assigned
168        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
183/// Returns a suitable unique name for the "state" property
184fn 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    /// Workaround: the expression can't be represented with the current data structure, so make it an error for now.
198    InvalidBecauseOfIssue1461,
199}
200
201/// Return the expression binding currently associated to the given property
202fn 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                    // Check that the expression is valid in the new scope
215                    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}