Skip to main content

i_slint_compiler/passes/
z_order.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// cSpell: ignore zorder
5/*! re-order the children by their z-order (static case) or mark elements
6    for dynamic z-order sorting (when z is bound to a non-constant expression).
7*/
8
9use std::cell::RefCell;
10use std::rc::Rc;
11
12use crate::diagnostics::Spanned;
13use crate::expression_tree::{BindingExpression, Expression, Unit};
14use crate::langtype::ElementType;
15use crate::object_tree::{Component, ElementRc};
16
17pub fn reorder_by_z_order(root_component: &Rc<Component>) {
18    crate::object_tree::recurse_elem_including_sub_components(
19        root_component,
20        &(),
21        &mut |elem: &ElementRc, _| {
22            reorder_children_by_zorder(elem);
23        },
24    )
25}
26
27fn reorder_children_by_zorder(elem: &Rc<RefCell<crate::object_tree::Element>>) {
28    let mut has_any_z = false;
29    let mut has_dynamic_z = false;
30
31    for child_elm in elem.borrow().children.iter() {
32        if mark_per_instance_z(child_elm) {
33            has_any_z = true;
34            has_dynamic_z = true;
35        } else if let Some(value) = z_binding_value(child_elm) {
36            has_any_z = true;
37            has_dynamic_z |= value.is_none();
38        }
39    }
40
41    if !has_any_z {
42        return;
43    }
44
45    if has_dynamic_z {
46        setup_dynamic_z_order(elem);
47    } else {
48        reorder_static_z(elem);
49    }
50}
51
52/// Mark a repeated child with a non-constant z for per-instance ordering; return true if marked.
53fn mark_per_instance_z(child_elm: &ElementRc) -> bool {
54    use crate::namedreference::NamedReference;
55
56    let child = child_elm.borrow();
57    if child.repeated.is_none() {
58        return false;
59    }
60    let ElementType::Component(c) = &child.base_type else { return false };
61    let c = c.clone();
62    drop(child);
63
64    {
65        let root = c.root_element.borrow();
66        let Some(b) = root.binding("z") else { return false };
67        if try_eval_const_expr(&b.expression).is_some() {
68            // Constant z: the whole repeater is ordered among its siblings
69            return false;
70        }
71    }
72
73    child_elm.borrow_mut().z_order = Some(crate::object_tree::ZOrder::PerInstance(
74        NamedReference::new(&c.root_element, smol_str::SmolStr::new_static("z")),
75    ));
76    // The z property is read by the repeater at runtime; keep it materialized
77    c.root_element
78        .borrow()
79        .property_analysis
80        .borrow_mut()
81        .entry(smol_str::SmolStr::new_static("z"))
82        .or_default()
83        .is_read = true;
84    true
85}
86
87/// Static z-order: evaluate all z values at compile time and reorder children.
88/// All z bindings are constant here: a non-constant one makes `reorder_children_by_zorder`
89/// take the dynamic path instead.
90fn reorder_static_z(elem: &Rc<RefCell<crate::object_tree::Element>>) {
91    let take_constant_z = |elem: &ElementRc| -> Option<f64> {
92        // Only take real bindings; `binding()` filters synthetic debug hooks,
93        // which the detection did not see either.
94        elem.borrow().binding("z")?;
95        let e = elem.borrow_mut().take_binding("z")?;
96        let z = try_eval_const_expr(&e.expression);
97        debug_assert!(z.is_some(), "non-constant z on the static path");
98        Some(z.unwrap_or(0.))
99    };
100    let mut children_z_order = Vec::new();
101    for (idx, child_elm) in elem.borrow().children.iter().enumerate() {
102        let z = take_constant_z(child_elm);
103        let z = z.or_else(|| {
104            child_elm.borrow().repeated.as_ref()?;
105            if let ElementType::Component(c) = &child_elm.borrow().base_type {
106                take_constant_z(&c.root_element)
107            } else {
108                None
109            }
110        });
111
112        if let Some(z) = z {
113            if children_z_order.is_empty() {
114                for i in 0..idx {
115                    children_z_order.push((i, 0.));
116                }
117            }
118            children_z_order.push((idx, z));
119        } else if !children_z_order.is_empty() {
120            children_z_order.push((idx, 0.));
121        }
122    }
123
124    if !children_z_order.is_empty() {
125        children_z_order.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap());
126
127        let new_children = children_z_order
128            .into_iter()
129            .map(|(idx, _)| elem.borrow().children[idx].clone())
130            .collect();
131        elem.borrow_mut().children = new_children;
132    }
133}
134
135/// Dynamic z-order: set the `z_order` of every child. Children whose z is a runtime
136/// value get a NamedReference to their z property (materialized as a runtime property),
137/// the other children a compile-time constant.
138fn setup_dynamic_z_order(elem: &Rc<RefCell<crate::object_tree::Element>>) {
139    use crate::namedreference::NamedReference;
140    use crate::object_tree::ZOrder;
141
142    for child_elm in elem.borrow().children.iter() {
143        if child_elm.borrow().z_order.is_some() {
144            // A repeated element with per-instance z, already set up by
145            // `mark_per_instance_z`
146            continue;
147        }
148        let z_order = if child_elm.borrow().repeated.is_some() {
149            // Repeater/conditional child with a constant z (a non-constant z is
150            // handled per instance by `mark_per_instance_z`): the whole repeater
151            // is ordered among its siblings.
152            let mut z_val = 0.;
153            if let ElementType::Component(c) = &child_elm.borrow().base_type {
154                // Only take real bindings; `binding()` filters synthetic debug hooks
155                let has_binding = c.root_element.borrow().binding("z").is_some();
156                if has_binding && let Some(e) = c.root_element.borrow_mut().take_binding("z") {
157                    let z = try_eval_const_expr(&e.expression);
158                    debug_assert!(z.is_some(), "handled by mark_per_instance_z otherwise");
159                    z_val = z.unwrap_or(0.);
160                }
161            }
162            ZOrder::Constant(z_val as f32)
163        } else if let Some(z_val) = constant_z(child_elm) {
164            child_elm.borrow_mut().take_binding("z");
165            ZOrder::Constant(z_val as f32)
166        } else {
167            // The z value is read at runtime; make sure a binding exists so that the
168            // property is materialized.
169            if child_elm.borrow().binding_cell_including_synthetic("z").is_none() {
170                let span = child_elm.borrow().to_source_location();
171                child_elm.borrow_mut().set_binding(
172                    smol_str::SmolStr::new_static("z"),
173                    BindingExpression::new_with_span(
174                        Expression::NumberLiteral(0., Unit::None),
175                        span,
176                    ),
177                );
178            }
179            ZOrder::Dynamic(NamedReference::new(child_elm, smol_str::SmolStr::new_static("z")))
180        };
181        child_elm.borrow_mut().z_order = Some(z_order);
182    }
183}
184
185/// The z binding of a child element, also looking into the root of repeated components:
186/// `None` if there is no z binding, `Some(None)` if the value is not a constant expression,
187/// `Some(Some(v))` for a constant.
188fn z_binding_value(child_elm: &ElementRc) -> Option<Option<f64>> {
189    let child = child_elm.borrow();
190    if let Some(b) = child.binding("z") {
191        return Some(try_eval_const_expr(&b.expression));
192    }
193    if child.repeated.is_some()
194        && let ElementType::Component(c) = &child.base_type
195        && let Some(b) = c.root_element.borrow().binding("z")
196    {
197        return Some(try_eval_const_expr(&b.expression));
198    }
199    None
200}
201
202/// The z value of a non-repeated child if it is known at compile time and cannot
203/// change at runtime (no z binding at all means the default of 0)
204fn constant_z(child_elm: &ElementRc) -> Option<f64> {
205    let child = child_elm.borrow();
206    if child.property_analysis.borrow().get("z").is_some_and(|a| a.is_set || a.is_linked) {
207        return None;
208    }
209    match child.binding("z") {
210        None => Some(0.),
211        Some(b) => {
212            if b.two_way_bindings.is_empty() && b.animation.is_none() {
213                try_eval_const_expr(&b.expression)
214            } else {
215                None
216            }
217        }
218    }
219}
220
221fn try_eval_const_expr(expression: &Expression) -> Option<f64> {
222    match expression.ignore_debug_hooks() {
223        Expression::NumberLiteral(v, Unit::None) => Some(*v),
224        Expression::Cast { from, .. } => try_eval_const_expr(from),
225        Expression::UnaryOp { sub, op: '-' } => try_eval_const_expr(sub).map(|v| -v),
226        Expression::UnaryOp { sub, op: '+' } => try_eval_const_expr(sub),
227        _ => None,
228    }
229}