From d4273f2328b03a96b4294ca8e68ab2920d230325 Mon Sep 17 00:00:00 2001 From: ruminorix Date: Tue, 5 May 2026 11:48:49 +0000 Subject: [PATCH 1/9] compiler: support dynamic z-ordering Allow the `z` property to accept dynamic expressions (e.g., `z: is_selected ? 10 : 0`) so element stacking order can change at runtime. Previously z was evaluated at compile time and removed. The implementation follows the geometry property materialization pattern: z bindings are kept on children, `materialize_fake_properties` creates declared Property fields, and `move_declarations` moves them to the component root. At runtime the code generator reads z values, sorts child indices, and traverses children in sorted order. Repeater and conditional children (for/if) participate with their z value evaluated at compile time when constant, or defaulting to 0 otherwise. All three backends are supported: Rust code generator, C++ code generator (via new FFI functions), and interpreter (via z_order_info on ItemTreeDescription). Closes: #221 --- api/rs/slint/private_unstable_api.rs | 3 +- internal/compiler/generator/cpp.rs | 65 +++++- internal/compiler/generator/rust.rs | 79 ++++++- internal/compiler/llr/item_tree.rs | 13 ++ internal/compiler/llr/lower_to_item_tree.rs | 30 +++ .../llr/optim_passes/count_property_use.rs | 29 +++ .../llr/optim_passes/remove_unused.rs | 21 ++ internal/compiler/object_tree.rs | 15 ++ internal/compiler/passes/inlining.rs | 3 + .../passes/optimize_useless_rectangles.rs | 5 + .../compiler/passes/repeater_component.rs | 5 + internal/compiler/passes/windows.rs | 3 + internal/compiler/passes/z_order.rs | 144 +++++++++++-- internal/core/item_tree.rs | 108 ++++++++++ internal/interpreter/dynamic_item_tree.rs | 106 ++++++++-- tests/cases/children/zorder_dynamic.slint | 195 ++++++++++++++++++ .../cases/basic/zorder_dynamic.slint | 69 +++++++ .../software/basic/zorder_dynamic.png | Bin 0 -> 385 bytes 18 files changed, 861 insertions(+), 32 deletions(-) create mode 100644 tests/cases/children/zorder_dynamic.slint create mode 100644 tests/screenshots/cases/basic/zorder_dynamic.slint create mode 100644 tests/screenshots/references/software/basic/zorder_dynamic.png diff --git a/api/rs/slint/private_unstable_api.rs b/api/rs/slint/private_unstable_api.rs index cf081cd1068..e1fa8c4ece7 100644 --- a/api/rs/slint/private_unstable_api.rs +++ b/api/rs/slint/private_unstable_api.rs @@ -188,7 +188,8 @@ pub mod re_exports { }; pub use i_slint_core::item_tree::{ ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak, TraversalOrder, - VisitChildrenResult, visit_item_tree, + VisitChildrenResult, compute_sorted_children_by_z, visit_item_tree, + visit_item_tree_with_sorted_children, }; pub use i_slint_core::items::{Transform, *}; pub use i_slint_core::layout::*; diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 47cae60bcc3..4a37b45b6b5 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -1594,6 +1594,12 @@ fn generate_item_tree( let mut item_tree_array: Vec = Default::default(); let mut item_array: Vec = Default::default(); + let mut z_sorted_nodes: Vec<( + usize, + Vec, + Vec<(u32, llr::ZChildSource)>, + )> = Vec::new(); + let mut current_node_index: usize = 0; sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| { let parent_index = parent_index as u32; @@ -1647,6 +1653,15 @@ fn generate_item_tree( )); } } + + if let Some(ref z_prop) = node.z_sort_order_property { + z_sorted_nodes.push(( + current_node_index, + node.sub_component_path.clone(), + z_prop.clone(), + )); + } + current_node_index += 1; }); let mut visit_children_statements = vec![ @@ -1674,10 +1689,56 @@ fn generate_item_tree( visit_children_statements.extend([ "};".into(), - format!("auto self_rc = reinterpret_cast(component.instance)->self_weak.lock()->into_dyn();"), - "return slint::cbindgen_private::slint_visit_item_tree(&self_rc, get_item_tree(component) , index, order, visitor, dyn_visit);".to_owned(), + format!("auto self = reinterpret_cast(component.instance);"), + "auto self_rc = self->self_weak.lock()->into_dyn();".into(), ]); + if !z_sorted_nodes.is_empty() { + visit_children_statements.push("switch (index) {".into()); + for (node_idx, node_sub_component_path, child_z_refs) in &z_sorted_nodes { + let count = child_z_refs.len(); + let z_reads: Vec = child_z_refs + .iter() + .map(|(_, z_source)| match z_source { + llr::ZChildSource::Constant(val) => format!("{val:.1}f"), + llr::ZChildSource::Property(member_ref) => match member_ref { + llr::MemberReference::Relative { parent_level: 0, local_reference } => { + let full_path: Vec<_> = node_sub_component_path + .iter() + .chain(local_reference.sub_component_path.iter()) + .copied() + .collect(); + match &local_reference.reference { + llr::LocalMemberIndex::Property(property_index) => { + let (compo_path, sub_component) = + follow_sub_component_path(root, sub_tree.root, &full_path); + let property_name = + ident(&sub_component.properties[*property_index].name); + format!("self->{compo_path}{property_name}.get()") + } + _ => "0.0f".into(), + } + } + _ => "0.0f".into(), + }, + }) + .collect(); + let z_list = z_reads.join(", "); + visit_children_statements.push(format!("case {node_idx}: {{")); + visit_children_statements.push(format!(" float z_values[{count}] = {{{z_list}}};")); + visit_children_statements.push(format!(" auto sorted = slint::cbindgen_private::slint_compute_sorted_children_by_z(slint::cbindgen_private::Slice{{z_values, {count}}});")); + visit_children_statements.push(" return slint::cbindgen_private::slint_visit_item_tree_with_sorted_children(&self_rc, get_item_tree(component), index, order, visitor, dyn_visit, slint::cbindgen_private::Slice{sorted.begin(), sorted.size()});".into()); + visit_children_statements.push("}".into()); + } + visit_children_statements.push("default:".into()); + visit_children_statements.push(" return slint::cbindgen_private::slint_visit_item_tree(&self_rc, get_item_tree(component), index, order, visitor, dyn_visit);".into()); + visit_children_statements.push("}".into()); + } else { + visit_children_statements.push( + "return slint::cbindgen_private::slint_visit_item_tree(&self_rc, get_item_tree(component), index, order, visitor, dyn_visit);".into(), + ); + } + target_struct.members.push(( Access::Private, Declaration::Function(Function { diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 244e68d9f83..6529db9535e 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -1948,10 +1948,24 @@ fn generate_item_tree( })); let mut item_tree_array = Vec::new(); let mut item_array = Vec::new(); + // Collect nodes that have dynamic z-ordering (node_index, sub_component_path, z_sort_order_property) + let mut z_sorted_nodes: Vec<( + usize, + Vec, + Vec<(u32, llr::ZChildSource)>, + )> = Vec::new(); + let mut current_node_index: usize = 0; sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| { let parent_index = parent_index as u32; + let node_idx = current_node_index; + current_node_index += 1; let (path, component) = follow_sub_component_path(root, sub_tree.root, &node.sub_component_path); + + if let Some(ref z_prop) = node.z_sort_order_property { + z_sorted_nodes.push((node_idx, node.sub_component_path.clone(), z_prop.clone())); + } + match node.item_index { Either::Right(mut repeater_index) => { assert_eq!(node.children.len(), 0); @@ -2046,6 +2060,69 @@ fn generate_item_tree( ) }; + // Generate visit_children_item body, potentially with z-sorted branches + let z_sorted_visit_body = if z_sorted_nodes.is_empty() { + quote! { + return sp::visit_item_tree(self, &sp::VRcMapped::origin(&self.as_ref().self_weak.get().unwrap().upgrade().unwrap()), self.get_item_tree().as_slice(), index, order, visitor, visit_dynamic, None); + } + } else { + // Generate match arms for z-sorted nodes + let mut z_match_arms = Vec::new(); + for (node_idx, node_sub_component_path, child_z_refs) in &z_sorted_nodes { + let idx_lit = *node_idx as isize; + let count = child_z_refs.len(); + let z_reads: Vec<_> = child_z_refs.iter().map(|(_, z_source)| { + match z_source { + llr::ZChildSource::Constant(val) => { + quote!(#val) + } + llr::ZChildSource::Property(member_ref) => { + match member_ref { + llr::MemberReference::Relative { parent_level: 0, local_reference } => { + let full_path: Vec<_> = node_sub_component_path.iter() + .chain(local_reference.sub_component_path.iter()) + .copied() + .collect(); + match &local_reference.reference { + llr::LocalMemberIndex::Property(property_index) => { + let (compo_path, sub_component) = follow_sub_component_path( + root, + sub_tree.root, + &full_path, + ); + let component_id = self::inner_component_id(sub_component); + let property_name = ident(&sub_component.properties[*property_index].name); + let property_field = + access_component_field_offset(&component_id, &property_name); + quote!((#compo_path #property_field).apply_pin(self).get() as f32) + } + _ => quote!(0.0f32), + } + } + _ => quote!(0.0f32), + } + } + } + }).collect(); + z_match_arms.push(quote! { + #idx_lit => { + let z_values: [f32; #count] = [#(#z_reads),*]; + let mut sorted = sp::SharedVector::default(); + sp::compute_sorted_children_by_z(&z_values, &mut sorted); + return sp::visit_item_tree(self, &sp::VRcMapped::origin(&self.as_ref().self_weak.get().unwrap().upgrade().unwrap()), self.get_item_tree().as_slice(), index, order, visitor, visit_dynamic, Some(sorted.as_slice())); + } + }); + } + quote! { + match index { + #(#z_match_arms)* + _ => { + return sp::visit_item_tree(self, &sp::VRcMapped::origin(&self.as_ref().self_weak.get().unwrap().upgrade().unwrap()), self.get_item_tree().as_slice(), index, order, visitor, visit_dynamic, None); + } + } + } + }; + quote!( #sub_comp @@ -2087,7 +2164,7 @@ fn generate_item_tree( fn visit_children_item(self: ::core::pin::Pin<&Self>, index: isize, order: sp::TraversalOrder, visitor: sp::ItemVisitorRefMut<'_>) -> sp::VisitChildrenResult { - return sp::visit_item_tree(self, &sp::VRcMapped::origin(&self.as_ref().self_weak.get().unwrap().upgrade().unwrap()), self.get_item_tree().as_slice(), index, order, visitor, visit_dynamic); + #z_sorted_visit_body #[allow(unused)] fn visit_dynamic(_self: ::core::pin::Pin<&#inner_component_id>, order: sp::TraversalOrder, visitor: sp::ItemVisitorRefMut<'_>, dyn_index: u32) -> sp::VisitChildrenResult { _self.visit_dynamic_children(dyn_index, order, visitor) diff --git a/internal/compiler/llr/item_tree.rs b/internal/compiler/llr/item_tree.rs index 95dc4178bbd..0242f594259 100644 --- a/internal/compiler/llr/item_tree.rs +++ b/internal/compiler/llr/item_tree.rs @@ -396,6 +396,19 @@ pub struct TreeNode { pub item_index: itertools::Either, pub children: Vec, pub is_accessible: bool, + /// If set, this node's children have dynamic z-ordering. + /// Each entry is (child_offset, source_of_z_value). + /// The code generator will read these z values at runtime and sort children accordingly. + pub z_sort_order_property: Option>, +} + +/// Source of a child's z value for dynamic z-ordering. +#[derive(Debug, Clone)] +pub enum ZChildSource { + /// Z value read from a property at runtime. + Property(super::MemberReference), + /// Z value known at compile time (e.g., constant z on a repeater child). + Constant(f32), } impl TreeNode { diff --git a/internal/compiler/llr/lower_to_item_tree.rs b/internal/compiler/llr/lower_to_item_tree.rs index 371b5af1f13..51d1604210e 100644 --- a/internal/compiler/llr/lower_to_item_tree.rs +++ b/internal/compiler/llr/lower_to_item_tree.rs @@ -1083,6 +1083,32 @@ fn make_tree( let e = element.borrow(); let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path)); let repeater_count = component.mapping.repeater_count; + + // Check if this element has dynamic z-ordering. If so, build the z sources: + // - Non-repeater children: NamedReferences resolved via property_mapping + // - Repeater children: constant values stored at compile time + let z_sort_order_property = if e.has_dynamic_z_order { + let children_count = e.children.len(); + let mut z_sources: Vec<(u32, ZChildSource)> = Vec::with_capacity(children_count); + // Track which child indices have repeater constants + let mut ref_idx = 0; + for child_idx in 0..children_count { + if let Some((_, val)) = + e.dynamic_z_child_constants.iter().find(|(i, _)| *i == child_idx) + { + z_sources.push((child_idx as u32, ZChildSource::Constant(*val))); + } else { + let z_nr = &e.dynamic_z_child_refs[ref_idx]; + let member_ref = component.mapping.map_property_reference(z_nr, state); + z_sources.push((child_idx as u32, ZChildSource::Property(member_ref))); + ref_idx += 1; + } + } + Some(z_sources) + } else { + None + }; + match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() { LoweredElement::SubComponent { sub_component_index } => { let sub_component = e.sub_component().unwrap(); @@ -1099,6 +1125,7 @@ fn make_tree( ); tree_node.children.extend(children); tree_node.is_accessible |= !e.accessibility_props.0.is_empty(); + tree_node.z_sort_order_property = z_sort_order_property; tree_node } LoweredElement::NativeItem { item_index } => TreeNode { @@ -1106,18 +1133,21 @@ fn make_tree( sub_component_path: sub_component_path.into(), item_index: itertools::Either::Left(*item_index), children: children.collect(), + z_sort_order_property, }, LoweredElement::Repeated { repeated_index } => TreeNode { is_accessible: false, sub_component_path: sub_component_path.into(), item_index: itertools::Either::Right(usize::from(*repeated_index) as u32), children: Vec::new(), + z_sort_order_property: None, }, LoweredElement::ComponentPlaceholder { repeated_index } => TreeNode { is_accessible: false, sub_component_path: sub_component_path.into(), item_index: itertools::Either::Right(*repeated_index + repeater_count), children: Vec::new(), + z_sort_order_property: None, }, } } diff --git a/internal/compiler/llr/optim_passes/count_property_use.rs b/internal/compiler/llr/optim_passes/count_property_use.rs index 2889d714bb8..ab7d6f1e318 100644 --- a/internal/compiler/llr/optim_passes/count_property_use.rs +++ b/internal/compiler/llr/optim_passes/count_property_use.rs @@ -152,6 +152,35 @@ pub fn count_property_use(root: &CompilationUnit) { visit_property(&p.activated, &ctx); } + // Visit z_sort_order_property references in tree nodes + fn visit_tree_z_properties( + node: &crate::llr::TreeNode, + root: &CompilationUnit, + ctx: &EvaluationContext, + ) { + if let Some(z_props) = &node.z_sort_order_property { + for (_, z_source) in z_props { + if let crate::llr::ZChildSource::Property(member_ref) = z_source { + visit_property(member_ref, ctx); + } + } + } + for child in &node.children { + visit_tree_z_properties(child, root, ctx); + } + } + for c in &root.public_components { + let ctx = EvaluationContext::new_sub_component(root, c.item_tree.root, (), None); + visit_tree_z_properties(&c.item_tree.tree, root, &ctx); + } + // Also visit z properties in repeated element sub_trees + root.for_each_sub_components(&mut |sc, _ctx| { + for r in &sc.repeated { + let rep_ctx = EvaluationContext::new_sub_component(root, r.sub_tree.root, (), None); + visit_tree_z_properties(&r.sub_tree.tree, root, &rep_ctx); + } + }); + clean_unused_bindings(root); } diff --git a/internal/compiler/llr/optim_passes/remove_unused.rs b/internal/compiler/llr/optim_passes/remove_unused.rs index 86c6be3099a..87020446d70 100644 --- a/internal/compiler/llr/optim_passes/remove_unused.rs +++ b/internal/compiler/llr/optim_passes/remove_unused.rs @@ -277,6 +277,25 @@ mod visitor { for p in public_properties { visit_public_property(p, &scope, state, visitor); } + visit_tree_node_z_properties(&mut item_tree.tree, &scope, state, visitor); + } + + fn visit_tree_node_z_properties( + node: &mut crate::llr::TreeNode, + scope: &EvaluationScope, + state: &VisitorState, + visitor: &mut (impl Visitor + ?Sized), + ) { + if let Some(z_props) = &mut node.z_sort_order_property { + for (_, z_source) in z_props { + if let crate::llr::ZChildSource::Property(member_ref) = z_source { + visit_member_reference(member_ref, scope, state, visitor); + } + } + } + for child in &mut node.children { + visit_tree_node_z_properties(child, scope, state, visitor); + } } pub fn visit_sub_component( @@ -338,6 +357,8 @@ mod visitor { visitor.visit_property_idx(data_prop, &inner_scope, state); } + visit_tree_node_z_properties(&mut sub_tree.tree, &inner_scope, state, visitor); + if let Some(listview) = listview { visit_member_reference(&mut listview.viewport_y, &scope, state, visitor); visit_member_reference(&mut listview.viewport_height, &scope, state, visitor); diff --git a/internal/compiler/object_tree.rs b/internal/compiler/object_tree.rs index 5498b3b76a9..529fa3f4c2a 100644 --- a/internal/compiler/object_tree.rs +++ b/internal/compiler/object_tree.rs @@ -812,6 +812,15 @@ pub struct Element { /// This element is a placeholder to embed an Component at pub is_component_placeholder: bool, + /// This element's children have dynamic z-ordering (z bound to non-constant expressions) + pub has_dynamic_z_order: bool, + /// NamedReferences to non-repeater children's z properties (for dynamic z-ordering). + /// Stored here so they get fixed up by move_declarations. + pub dynamic_z_child_refs: Vec, + /// Compile-time z values for repeater/conditional children (child_index, z_value). + /// These children can't have their z materialized on the parent component. + pub dynamic_z_child_constants: Vec<(usize, f32)>, + pub states: Vec, pub transitions: Vec, @@ -2645,6 +2654,12 @@ pub fn visit_all_named_references_in_element( elem.borrow_mut().geometry_props = Some(geometry_props); } + let mut dynamic_z_child_refs = std::mem::take(&mut elem.borrow_mut().dynamic_z_child_refs); + for r in &mut dynamic_z_child_refs { + vis(r); + } + elem.borrow_mut().dynamic_z_child_refs = dynamic_z_child_refs; + // visit two way bindings for expr in elem.borrow().bindings.values() { for twb in &mut expr.borrow_mut().two_way_bindings { diff --git a/internal/compiler/passes/inlining.rs b/internal/compiler/passes/inlining.rs index cd536ea7f15..f8a64f63d29 100644 --- a/internal/compiler/passes/inlining.rs +++ b/internal/compiler/passes/inlining.rs @@ -401,6 +401,9 @@ fn duplicate_element_with_mapping( item_index_of_first_children: Default::default(), is_flickable_viewport: elem.is_flickable_viewport, has_popup_child: elem.has_popup_child, + has_dynamic_z_order: elem.has_dynamic_z_order, + dynamic_z_child_refs: elem.dynamic_z_child_refs.clone(), + dynamic_z_child_constants: elem.dynamic_z_child_constants.clone(), is_legacy_syntax: elem.is_legacy_syntax, inline_depth: elem.inline_depth + 1, // Deep-clone grid_layout_cell to avoid sharing between original and inlined copies. diff --git a/internal/compiler/passes/optimize_useless_rectangles.rs b/internal/compiler/passes/optimize_useless_rectangles.rs index 5664a10efb5..ac3f296b354 100644 --- a/internal/compiler/passes/optimize_useless_rectangles.rs +++ b/internal/compiler/passes/optimize_useless_rectangles.rs @@ -61,6 +61,11 @@ fn can_optimize(elem: &ElementRc) -> bool { _ => return false, }; + // Don't optimize if the element has a z binding (needed for dynamic z-ordering) + if e.bindings.contains_key("z") { + return false; + } + let analysis = e.property_analysis.borrow(); for coord in ["x", "y"] { if e.bindings.contains_key(coord) || analysis.get(coord).is_some_and(|a| a.is_set) { diff --git a/internal/compiler/passes/repeater_component.rs b/internal/compiler/passes/repeater_component.rs index f3307f19491..d61657b2edd 100644 --- a/internal/compiler/passes/repeater_component.rs +++ b/internal/compiler/passes/repeater_component.rs @@ -59,6 +59,11 @@ fn create_repeater_components(component: &Rc) { geometry_props: original_elem.geometry_props.clone(), is_flickable_viewport: original_elem.is_flickable_viewport, has_popup_child: original_elem.has_popup_child, + has_dynamic_z_order: original_elem.has_dynamic_z_order, + dynamic_z_child_refs: std::mem::take(&mut original_elem.dynamic_z_child_refs), + dynamic_z_child_constants: std::mem::take( + &mut original_elem.dynamic_z_child_constants, + ), item_index: Default::default(), // Not determined yet item_index_of_first_children: Default::default(), is_legacy_syntax: original_elem.is_legacy_syntax, diff --git a/internal/compiler/passes/windows.rs b/internal/compiler/passes/windows.rs index 60c5926c974..52b49157569 100644 --- a/internal/compiler/passes/windows.rs +++ b/internal/compiler/passes/windows.rs @@ -58,6 +58,9 @@ pub fn ensure_window( accessibility_props: Default::default(), geometry_props: Default::default(), is_flickable_viewport: false, + has_dynamic_z_order: false, + dynamic_z_child_refs: Vec::new(), + dynamic_z_child_constants: Vec::new(), item_index: Default::default(), item_index_of_first_children: Default::default(), grid_layout_cell: None, diff --git a/internal/compiler/passes/z_order.rs b/internal/compiler/passes/z_order.rs index 4852bd0a707..8dbcac77080 100644 --- a/internal/compiler/passes/z_order.rs +++ b/internal/compiler/passes/z_order.rs @@ -1,13 +1,15 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -/*! re-order the children by their z-order +/*! re-order the children by their z-order (static case) or mark elements + for dynamic z-order sorting (when z is bound to a non-constant expression). */ +use std::cell::RefCell; use std::rc::Rc; -use crate::diagnostics::BuildDiagnostics; -use crate::expression_tree::{Expression, Unit}; +use crate::diagnostics::{BuildDiagnostics, Spanned}; +use crate::expression_tree::{BindingExpression, Expression, Unit}; use crate::langtype::ElementType; use crate::object_tree::{Component, ElementRc}; @@ -22,10 +24,68 @@ pub fn reorder_by_z_order(root_component: &Rc, diag: &mut BuildDiagno } fn reorder_children_by_zorder( - elem: &Rc>, + elem: &Rc>, diag: &mut BuildDiagnostics, ) { - // maps indexes to their z order + let children_count = elem.borrow().children.len(); + if children_count == 0 { + return; + } + + // First pass: determine if we have any z properties and whether they're all constant + let mut has_any_z = false; + let mut has_dynamic_z = false; + + for child_elm in elem.borrow().children.iter() { + let has_z = child_elm.borrow().bindings.contains_key("z"); + let repeated_has_z = if !has_z { + child_elm.borrow().repeated.is_some() + && matches!(&child_elm.borrow().base_type, ElementType::Component(c) + if c.root_element.borrow().bindings.contains_key("z")) + } else { + false + }; + + if has_z || repeated_has_z { + has_any_z = true; + let is_const = if has_z { + child_elm + .borrow() + .bindings + .get("z") + .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) + .unwrap_or(false) + } else { + if let ElementType::Component(c) = &child_elm.borrow().base_type { + c.root_element + .borrow() + .bindings + .get("z") + .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) + .unwrap_or(false) + } else { + false + } + }; + if !is_const { + has_dynamic_z = true; + } + } + } + + if !has_any_z { + return; + } + + if has_dynamic_z { + setup_dynamic_z_order(elem, diag); + } else { + reorder_static_z(elem, diag); + } +} + +/// Static z-order: evaluate all z values at compile time and reorder children. +fn reorder_static_z(elem: &Rc>, diag: &mut BuildDiagnostics) { let mut children_z_order = Vec::new(); for (idx, child_elm) in elem.borrow().children.iter().enumerate() { let z = child_elm @@ -68,20 +128,76 @@ fn reorder_children_by_zorder( } } +/// Dynamic z-order: mark the parent element and ensure all children have z bindings. +/// Non-repeater children get NamedReferences (materialized as runtime properties). +/// Repeater/conditional children get their z evaluated at compile time if constant, +/// or default to z=0. +fn setup_dynamic_z_order( + elem: &Rc>, + _diag: &mut BuildDiagnostics, +) { + use crate::namedreference::NamedReference; + + elem.borrow_mut().has_dynamic_z_order = true; + + let mut z_refs = Vec::new(); + let mut z_constants = Vec::new(); + + for (idx, child_elm) in elem.borrow().children.iter().enumerate() { + if child_elm.borrow().repeated.is_some() { + // Repeater/conditional child: z lives in the inner component. + // Evaluate at compile time if constant, otherwise default to 0. + let z_val = if let ElementType::Component(c) = &child_elm.borrow().base_type { + c.root_element + .borrow_mut() + .bindings + .remove("z") + .and_then(|e| try_eval_const_expr(&e.borrow().expression)) + .unwrap_or(0.) + } else { + 0. + }; + z_constants.push((idx, z_val as f32)); + } else { + // Non-repeater child: create NamedReference for runtime access. + if !child_elm.borrow().bindings.contains_key("z") { + let span = child_elm.borrow().to_source_location(); + child_elm.borrow_mut().bindings.insert( + smol_str::SmolStr::new_static("z"), + BindingExpression::new_with_span( + Expression::NumberLiteral(0., Unit::None), + span, + ) + .into(), + ); + } + z_refs.push(NamedReference::new(child_elm, smol_str::SmolStr::new_static("z"))); + } + } + let mut e = elem.borrow_mut(); + e.dynamic_z_child_refs = z_refs; + e.dynamic_z_child_constants = z_constants; +} + +fn try_eval_const_expr(expression: &Expression) -> Option { + match super::ignore_debug_hooks(expression) { + Expression::NumberLiteral(v, Unit::None) => Some(*v), + Expression::Cast { from, .. } => try_eval_const_expr(from), + Expression::UnaryOp { sub, op: '-' } => try_eval_const_expr(sub).map(|v| -v), + Expression::UnaryOp { sub, op: '+' } => try_eval_const_expr(sub), + _ => None, + } +} + fn eval_const_expr( expression: &Expression, name: &str, span: &dyn crate::diagnostics::Spanned, diag: &mut BuildDiagnostics, ) -> Option { - match super::ignore_debug_hooks(expression) { - Expression::NumberLiteral(v, Unit::None) => Some(*v), - Expression::Cast { from, .. } => eval_const_expr(from, name, span, diag), - Expression::UnaryOp { sub, op: '-' } => eval_const_expr(sub, name, span, diag).map(|v| -v), - Expression::UnaryOp { sub, op: '+' } => eval_const_expr(sub, name, span, diag), - _ => { - diag.push_error(format!("'{name}' must be an number literal"), span); - None - } + let result = try_eval_const_expr(expression); + if result.is_none() { + diag.push_error(format!("'{name}' must be an number literal"), span); } + result } diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index e56d9b66b8d..27c5fe0a3b0 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1398,6 +1398,80 @@ pub fn visit_item_tree( } } +/// Compute a sorted list of child indices based on their z values. +/// Returns a SharedVector where each entry is a child offset (relative to children_index) +/// sorted by the corresponding z value (stable sort). +pub fn compute_sorted_children_by_z(z_values: &[f32]) -> crate::SharedVector { + let mut indices: alloc::vec::Vec = (0..z_values.len() as u32).collect(); + indices.sort_by(|&a, &b| { + z_values[a as usize] + .partial_cmp(&z_values[b as usize]) + .unwrap_or(core::cmp::Ordering::Equal) + }); + indices.iter().map(|&i| i as f32).collect() +} + +/// Like `visit_item_tree`, but uses a pre-computed sorted order for children instead of +/// the sequential order from the item tree array. +/// `sorted_children_offsets` contains child offsets (0-based relative to children_index) +/// in the desired visitation order (back-to-front). +pub fn visit_item_tree_with_sorted_children( + base: Pin<&Base>, + item_tree: &ItemTreeRc, + item_tree_array: &[ItemTreeNode], + index: isize, + order: TraversalOrder, + mut visitor: vtable::VRefMut, + visit_dynamic: impl Fn( + Pin<&Base>, + TraversalOrder, + vtable::VRefMut, + u32, + ) -> VisitChildrenResult, + sorted_children_offsets: &[f32], +) -> VisitChildrenResult { + let mut visit_at_index = |idx: u32| -> VisitChildrenResult { + match &item_tree_array[idx as usize] { + ItemTreeNode::Item { .. } => { + let item = crate::items::ItemRc::new(item_tree.clone(), idx); + visitor.visit_item(item_tree, idx, item.borrow()) + } + ItemTreeNode::DynamicTree { index, .. } => { + if let Some(sub_idx) = + visit_dynamic(base, order, visitor.borrow_mut(), *index).aborted_index() + { + VisitChildrenResult::abort(idx, sub_idx) + } else { + VisitChildrenResult::CONTINUE + } + } + } + }; + if index == -1 { + visit_at_index(0) + } else { + match &item_tree_array[index as usize] { + ItemTreeNode::Item { children_index, children_count, .. } => { + let count = sorted_children_offsets.len().min(*children_count as usize); + for i in 0..count { + let offset_idx = match order { + TraversalOrder::BackToFront => i, + TraversalOrder::FrontToBack => count - 1 - i, + }; + let child_offset = sorted_children_offsets[offset_idx] as u32; + let idx = *children_index + child_offset; + let maybe_abort_index = visit_at_index(idx); + if maybe_abort_index.has_aborted() { + return maybe_abort_index; + } + } + } + ItemTreeNode::DynamicTree { .. } => panic!("should not be called with dynamic items"), + }; + VisitChildrenResult::CONTINUE + } +} + #[cfg(feature = "ffi")] pub(crate) mod ffi { #![allow(unsafe_code)] @@ -1462,6 +1536,40 @@ pub(crate) mod ffi { |a, b, c, d| visit_dynamic(a.get_ref() as *const vtable::Dyn as *const c_void, b, c, d), ) } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn slint_visit_item_tree_with_sorted_children( + item_tree: &ItemTreeRc, + item_tree_array: Slice, + index: isize, + order: TraversalOrder, + visitor: VRefMut, + visit_dynamic: extern "C" fn( + base: *const c_void, + order: TraversalOrder, + visitor: vtable::VRefMut, + dyn_index: u32, + ) -> VisitChildrenResult, + sorted_children_offsets: Slice, + ) -> VisitChildrenResult { + crate::item_tree::visit_item_tree_with_sorted_children( + VRc::as_pin_ref(item_tree), + item_tree, + item_tree_array.as_slice(), + index, + order, + visitor, + |a, b, c, d| visit_dynamic(a.get_ref() as *const vtable::Dyn as *const c_void, b, c, d), + sorted_children_offsets.as_slice(), + ) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn slint_compute_sorted_children_by_z( + z_values: Slice, + ) -> crate::SharedVector { + crate::item_tree::compute_sorted_children_by_z(z_values.as_slice()) + } } #[cfg(test)] diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index e34675ae0f5..d764c8b3341 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -442,6 +442,15 @@ impl<'id> From>> for ErasedItemTreeDescription { /// ItemTreeDescription is a representation of a ItemTree suitable for interpretation /// +/// Z info for a single child in a dynamically z-ordered parent. +#[derive(Clone)] +enum ZInterpreterChildInfo { + /// Z from a runtime property. Stores the property name on the root element. + Property(SmolStr), + /// Z known at compile time (repeater/conditional children). + Constant(f32), +} + /// It contains information about how to create and destroy the Component. /// Its first member is the ItemTreeVTable for generated instance, since it is a `#[repr(C)]` /// structure, it is valid to cast a pointer to the ItemTreeVTable back to a @@ -485,6 +494,9 @@ pub struct ItemTreeDescription<'id> { /// The collection of compiled globals compiled_globals: Option>, + /// For tree nodes with dynamic z-ordering: maps tree_index to per-child z info. + z_order_info: HashMap>, + /// The type loader, which will be available only on the top-most `ItemTreeDescription`. /// All other `ItemTreeDescription`s have `None` here. #[cfg(feature = "internal-highlight")] @@ -808,6 +820,60 @@ extern "C" fn visit_children_item( generativity::make_guard!(guard); let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; let comp_rc = instance_ref.self_weak().get().unwrap().upgrade().unwrap(); + + // Macro-like helper to avoid duplicating the visit_dynamic closure + macro_rules! visit_dyn { + () => { + |_, order, visitor, index: u32| { + if (index as usize) >= instance_ref.description.repeater.len() { + VisitChildrenResult::CONTINUE + } else { + let rep_in_comp = + unsafe { instance_ref.description.repeater[index as usize].get_untagged() }; + ensure_repeater_updated(instance_ref, rep_in_comp); + let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance); + repeater.visit(order, visitor) + } + } + }; + } + + // Check if this node has dynamic z-ordering + if index >= 0 { + if let Some(z_children) = instance_ref.description.z_order_info.get(&(index as u32)) { + let z_values: Vec = z_children + .iter() + .map(|child_info| match child_info { + ZInterpreterChildInfo::Constant(val) => *val, + ZInterpreterChildInfo::Property(prop_name) => instance_ref + .description + .custom_properties + .get(prop_name) + .and_then(|p| unsafe { + p.prop + .get(core::pin::Pin::new_unchecked( + &*(instance_ref.as_ptr().add(p.offset)), + )) + .ok() + }) + .and_then(|v| if let Value::Number(n) = v { Some(n as f32) } else { None }) + .unwrap_or(0.0), + }) + .collect(); + let sorted = i_slint_core::item_tree::compute_sorted_children_by_z(&z_values); + return i_slint_core::item_tree::visit_item_tree_with_sorted_children( + instance_ref.instance, + &vtable::VRc::into_dyn(comp_rc), + get_item_tree(component).as_slice(), + index, + order, + v, + visit_dyn!(), + sorted.as_slice(), + ); + } + } + i_slint_core::item_tree::visit_item_tree( instance_ref.instance, &vtable::VRc::into_dyn(comp_rc), @@ -815,20 +881,7 @@ extern "C" fn visit_children_item( index, order, v, - |_, order, visitor, index| { - if index as usize >= instance_ref.description.repeater.len() { - // Do nothing: We are ComponentContainer and Our parent already did all the work! - VisitChildrenResult::CONTINUE - } else { - // `ensure_updated` needs a 'static lifetime so we must call get_untagged. - // Safety: we do not mix the component with other component id in this function - let rep_in_comp = - unsafe { instance_ref.description.repeater[index as usize].get_untagged() }; - ensure_repeater_updated(instance_ref, rep_in_comp); - let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance); - repeater.visit(order, visitor) - } - }, + visit_dyn!(), ) } @@ -1445,6 +1498,30 @@ pub(crate) fn generate_item_tree<'id>( drop_in_place, dealloc, }; + // Build z_order_info from original elements + let mut z_order_info: HashMap> = HashMap::new(); + for (idx, elem_rc) in builder.original_elements.iter().enumerate() { + let elem = elem_rc.borrow(); + if elem.has_dynamic_z_order { + let mut child_infos = Vec::new(); + let mut ref_idx = 0; + for (child_idx, _child) in elem.children.iter().enumerate() { + if let Some((_, val)) = + elem.dynamic_z_child_constants.iter().find(|(i, _)| *i == child_idx) + { + child_infos.push(ZInterpreterChildInfo::Constant(*val)); + } else if ref_idx < elem.dynamic_z_child_refs.len() { + let nr = &elem.dynamic_z_child_refs[ref_idx]; + child_infos.push(ZInterpreterChildInfo::Property(nr.name().clone())); + ref_idx += 1; + } else { + child_infos.push(ZInterpreterChildInfo::Constant(0.0)); + } + } + z_order_info.insert(idx as u32, child_infos); + } + } + let t = ItemTreeDescription { ct: t, dynamic_type: builder.type_builder.build(), @@ -1466,6 +1543,7 @@ pub(crate) fn generate_item_tree<'id>( timers, popup_ids: std::cell::RefCell::new(HashMap::new()), popup_menu_description: builder.popup_menu_description, + z_order_info, #[cfg(feature = "internal-highlight")] type_loader: std::cell::OnceCell::new(), #[cfg(feature = "internal-highlight")] diff --git a/tests/cases/children/zorder_dynamic.slint b/tests/cases/children/zorder_dynamic.slint new file mode 100644 index 00000000000..792c11dc077 --- /dev/null +++ b/tests/cases/children/zorder_dynamic.slint @@ -0,0 +1,195 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// Test dynamic z-ordering: z bound to expressions that change at runtime. +// Tests: property-bound z, multiple children with mixed static/dynamic z, +// and z values changing dynamically to reorder click targets. + +export component TestCase inherits Rectangle { + width: 300phx; + height: 100phx; + + in-out property clicked-value; + in-out property front: false; + in-out property top-layer: 0; + + // --- Section 1: Simple dynamic z swap (x: 0..100) --- + Rectangle { + x: 0phx; width: 100phx; height: 100phx; + + TouchArea { + width: 100%; height: 100%; + z: front ? 0 : 10; + clicked => { clicked-value = 1; } + } + TouchArea { + width: 100%; height: 100%; + z: front ? 10 : 0; + clicked => { clicked-value = 2; } + } + } + + // --- Section 2: Multiple layers with one dynamic top (x: 100..200) --- + // Four overlapping layers. top-layer selects which is on top (z=100). + // Others have z=0 so the last one in source order wins ties. + Rectangle { + x: 100phx; width: 100phx; height: 100phx; + + TouchArea { + width: 100%; height: 100%; + z: top-layer == 0 ? 100 : 0; + clicked => { clicked-value = 10; } + } + TouchArea { + width: 100%; height: 100%; + z: top-layer == 1 ? 100 : 0; + clicked => { clicked-value = 11; } + } + TouchArea { + width: 100%; height: 100%; + z: top-layer == 2 ? 100 : 0; + clicked => { clicked-value = 12; } + } + TouchArea { + width: 100%; height: 100%; + z: top-layer == 3 ? 100 : 0; + clicked => { clicked-value = 13; } + } + } + + // --- Section 3: Dynamic z mixed with static z children (x: 200..300) --- + // A static z=5 element and a dynamic z element that toggles above/below. + Rectangle { + x: 200phx; width: 100phx; height: 100phx; + + TouchArea { + width: 100%; height: 100%; + z: 5; + clicked => { clicked-value = 20; } + } + TouchArea { + width: 100%; height: 100%; + z: front ? 10 : 1; + clicked => { clicked-value = 21; } + } + } +} + +/* +```rust +let instance = TestCase::new().unwrap(); + +// Section 1: front=false, first area has z=10 (on top) +slint_testing::send_mouse_click(&instance, 50., 50.); +assert_eq!(instance.get_clicked_value(), 1); + +// Flip: second area now z=10 +instance.set_front(true); +slint_testing::send_mouse_click(&instance, 50., 50.); +assert_eq!(instance.get_clicked_value(), 2); + +// Section 2: top_layer=0, layer 0 is on top +instance.set_top_layer(0); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq!(instance.get_clicked_value(), 10); + +// top_layer=2, layer 2 on top +instance.set_top_layer(2); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq!(instance.get_clicked_value(), 12); + +// top_layer=3, layer 3 on top +instance.set_top_layer(3); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq!(instance.get_clicked_value(), 13); + +// top_layer=1, layer 1 on top +instance.set_top_layer(1); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq!(instance.get_clicked_value(), 11); + +// Section 3: front=true, dynamic element z=10 > static z=5 +instance.set_front(true); +slint_testing::send_mouse_click(&instance, 250., 50.); +assert_eq!(instance.get_clicked_value(), 21); + +// front=false, dynamic element z=1 < static z=5 +instance.set_front(false); +slint_testing::send_mouse_click(&instance, 250., 50.); +assert_eq!(instance.get_clicked_value(), 20); +``` + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; + +slint_testing::send_mouse_click(&instance, 50., 50.); +assert_eq(instance.get_clicked_value(), 1); + +instance.set_front(true); +slint_testing::send_mouse_click(&instance, 50., 50.); +assert_eq(instance.get_clicked_value(), 2); + +instance.set_top_layer(0); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq(instance.get_clicked_value(), 10); + +instance.set_top_layer(2); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq(instance.get_clicked_value(), 12); + +instance.set_top_layer(3); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq(instance.get_clicked_value(), 13); + +instance.set_top_layer(1); +slint_testing::send_mouse_click(&instance, 150., 50.); +assert_eq(instance.get_clicked_value(), 11); + +instance.set_front(true); +slint_testing::send_mouse_click(&instance, 250., 50.); +assert_eq(instance.get_clicked_value(), 21); + +instance.set_front(false); +slint_testing::send_mouse_click(&instance, 250., 50.); +assert_eq(instance.get_clicked_value(), 20); +``` + +```js +var instance = new slint.TestCase(); + +// Section 1 +slintlib.private_api.send_mouse_click(instance, 50., 50.); +assert.equal(instance.clicked_value, 1); + +instance.front = true; +slintlib.private_api.send_mouse_click(instance, 50., 50.); +assert.equal(instance.clicked_value, 2); + +// Section 2 +instance.top_layer = 0; +slintlib.private_api.send_mouse_click(instance, 150., 50.); +assert.equal(instance.clicked_value, 10); + +instance.top_layer = 2; +slintlib.private_api.send_mouse_click(instance, 150., 50.); +assert.equal(instance.clicked_value, 12); + +instance.top_layer = 3; +slintlib.private_api.send_mouse_click(instance, 150., 50.); +assert.equal(instance.clicked_value, 13); + +instance.top_layer = 1; +slintlib.private_api.send_mouse_click(instance, 150., 50.); +assert.equal(instance.clicked_value, 11); + +// Section 3 +instance.front = true; +slintlib.private_api.send_mouse_click(instance, 250., 50.); +assert.equal(instance.clicked_value, 21); + +instance.front = false; +slintlib.private_api.send_mouse_click(instance, 250., 50.); +assert.equal(instance.clicked_value, 20); +``` +*/ diff --git a/tests/screenshots/cases/basic/zorder_dynamic.slint b/tests/screenshots/cases/basic/zorder_dynamic.slint new file mode 100644 index 00000000000..9825b9bc406 --- /dev/null +++ b/tests/screenshots/cases/basic/zorder_dynamic.slint @@ -0,0 +1,69 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// Screenshot test for dynamic z-ordering. +// Several overlapping colored rectangles with z values assigned in init, +// mixed with if-conditional and for-repeater elements. + +export component TestCase inherits Window { + width: 64px; + height: 64px; + background: black; + + property show-green: true; + property z-red: 0; + property z-blue: 0; + + init => { + z-red = 1; + z-blue = 30; + } + + // Bottom-left: overlapping rects with z from init. + // Blue (z=30) covers red (z=1). + Rectangle { + x: 0px; y: 32px; width: 32px; height: 32px; + + Rectangle { + x: 2px; y: 2px; width: 20px; height: 20px; + background: red; + z: z-red; + } + Rectangle { + x: 8px; y: 8px; width: 20px; height: 20px; + background: blue; + z: z-blue; + } + } + + // Top-left: if-conditional green square on top of yellow. + Rectangle { + x: 0px; y: 0px; width: 32px; height: 32px; + + Rectangle { + x: 2px; y: 2px; width: 28px; height: 28px; + background: yellow; + z: 1; + } + if show-green: Rectangle { + x: 6px; y: 6px; width: 20px; height: 20px; + background: green; + z: 10; + } + } + + // Right half: for-repeater staircase with z from model data. + // Green (z=40) on top, then blue (z=23), then red (z=5). + Rectangle { + x: 32px; y: 0px; width: 32px; height: 64px; + + for dyn_z in [5, 40, 23] : Rectangle { + x: 0px; + y: dyn_z * 1px; + width: 24px; + height: 24px; + background: dyn_z == 5 ? #f00 : dyn_z == 40 ? #0f0 : #00f; + z: dyn_z; + } + } +} diff --git a/tests/screenshots/references/software/basic/zorder_dynamic.png b/tests/screenshots/references/software/basic/zorder_dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..3ce4f257fd54020bd4fa343f7715b2f0f2639ef7 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVD#{GaSW+oe0zX-flB1GV;}#Q zzw145W_gi!^19sH+n^}3XWlfg_4AC3mEQd<+n!amtlc{M>UySU|Mi>x-(O)f`E0#r z=YLL)fAz{g{`a1$H~u`|drv?}978SN|Al`d|MrYzq#GfL0SyeSRO|fT54HNie{-$#DgTl?|BE6-fKCK*7Jt$gcxFHC zhkOfA5kv^&SO{mupYqK={8i&5&?UeceIET+MVDCdr~AnNM1+U_bA;$ literal 0 HcmV?d00001 From a70562a4b65835dc0720c84e547b6f9e87565307 Mon Sep 17 00:00:00 2001 From: ruminorix Date: Tue, 5 May 2026 12:04:58 +0000 Subject: [PATCH 2/9] compiler: report error for dynamic z in repeaters Per-item dynamic z inside repeaters (e.g. `z: model_value`) is not yet supported. Emit a clear compile error instead of silently defaulting to z=0. Also update the screenshot test to avoid using dynamic z in repeaters. --- internal/compiler/passes/z_order.rs | 25 ++++++++++++------ .../syntax/elements/z_repeater_dynamic.slint | 14 ++++++++++ .../cases/basic/zorder_dynamic.slint | 23 ++++++++++------ .../software/basic/zorder_dynamic.png | Bin 385 -> 391 bytes 4 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 internal/compiler/tests/syntax/elements/z_repeater_dynamic.slint diff --git a/internal/compiler/passes/z_order.rs b/internal/compiler/passes/z_order.rs index 8dbcac77080..4d2249657b0 100644 --- a/internal/compiler/passes/z_order.rs +++ b/internal/compiler/passes/z_order.rs @@ -134,7 +134,7 @@ fn reorder_static_z(elem: &Rc>, diag: &mut /// or default to z=0. fn setup_dynamic_z_order( elem: &Rc>, - _diag: &mut BuildDiagnostics, + diag: &mut BuildDiagnostics, ) { use crate::namedreference::NamedReference; @@ -146,14 +146,23 @@ fn setup_dynamic_z_order( for (idx, child_elm) in elem.borrow().children.iter().enumerate() { if child_elm.borrow().repeated.is_some() { // Repeater/conditional child: z lives in the inner component. - // Evaluate at compile time if constant, otherwise default to 0. + // Must be a compile-time constant; per-item dynamic z in repeaters + // is not yet supported. let z_val = if let ElementType::Component(c) = &child_elm.borrow().base_type { - c.root_element - .borrow_mut() - .bindings - .remove("z") - .and_then(|e| try_eval_const_expr(&e.borrow().expression)) - .unwrap_or(0.) + let binding = c.root_element.borrow_mut().bindings.remove("z"); + if let Some(e) = binding { + if let Some(val) = try_eval_const_expr(&e.borrow().expression) { + val + } else { + diag.push_error( + "'z' in a repeated element must be a constant".into(), + &*e.borrow(), + ); + 0. + } + } else { + 0. + } } else { 0. }; diff --git a/internal/compiler/tests/syntax/elements/z_repeater_dynamic.slint b/internal/compiler/tests/syntax/elements/z_repeater_dynamic.slint new file mode 100644 index 00000000000..0b5181618b0 --- /dev/null +++ b/internal/compiler/tests/syntax/elements/z_repeater_dynamic.slint @@ -0,0 +1,14 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// Dynamic z in a repeater is not supported. + +export component Foo inherits Rectangle { + Rectangle { + Rectangle { z: root.width > 10px ? 5 : 0; } + for dyn_z in [5, 40, 23] : Rectangle { + z: dyn_z; +// > KcD6Qe!)Ng-JZ@oQ?K)JKNCXa!+&Weo`iqNo&PnS z*$YmsZ#4M7@yLG#u=IS800f?{elF{r5}E*N0(#g0 delta 213 zcmZo?Ze*UI!sszkwSMAs-ig!s>i5qy_`mZAx8sNZ(g*+Na~L$7t=H^KH$oC)`M*D~ zQmylUKgYj%ceIRG?Cw+lu_S1gIw*VDEgrGWroE3k{ zH~;Wgjgvr@aE2;d{K@Che^q1|ki?2V-ADc>0v#au=f4}!h5tFi|M1OLW@Vqt00f?{ KelF{r5}E+mO?GGi From 65e5eaea0b6bb82cfc9d29aed4d20307397a2ad3 Mon Sep 17 00:00:00 2001 From: ruminorix Date: Tue, 5 May 2026 12:43:58 +0000 Subject: [PATCH 3/9] compiler: address review feedback for dynamic z-ordering Move z-order info from parent vectors to per-child `z_order: Option` field, making it resilient to children reordering by other passes. Use `} else if` style. Add comment explaining why macro is needed in interpreter (generativity lifetime prevents using a regular closure). --- internal/compiler/llr/lower_to_item_tree.rs | 34 +++++++++---------- internal/compiler/object_tree.rs | 28 +++++++++------ internal/compiler/passes/inlining.rs | 3 +- .../compiler/passes/repeater_component.rs | 5 +-- internal/compiler/passes/windows.rs | 3 +- internal/compiler/passes/z_order.rs | 32 +++++++---------- internal/interpreter/dynamic_item_tree.rs | 31 ++++++++--------- 7 files changed, 66 insertions(+), 70 deletions(-) diff --git a/internal/compiler/llr/lower_to_item_tree.rs b/internal/compiler/llr/lower_to_item_tree.rs index 51d1604210e..1909b4400dd 100644 --- a/internal/compiler/llr/lower_to_item_tree.rs +++ b/internal/compiler/llr/lower_to_item_tree.rs @@ -1084,24 +1084,24 @@ fn make_tree( let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path)); let repeater_count = component.mapping.repeater_count; - // Check if this element has dynamic z-ordering. If so, build the z sources: - // - Non-repeater children: NamedReferences resolved via property_mapping - // - Repeater children: constant values stored at compile time + // Check if this element has dynamic z-ordering. If so, build the z sources + // from each child's z_order field. let z_sort_order_property = if e.has_dynamic_z_order { - let children_count = e.children.len(); - let mut z_sources: Vec<(u32, ZChildSource)> = Vec::with_capacity(children_count); - // Track which child indices have repeater constants - let mut ref_idx = 0; - for child_idx in 0..children_count { - if let Some((_, val)) = - e.dynamic_z_child_constants.iter().find(|(i, _)| *i == child_idx) - { - z_sources.push((child_idx as u32, ZChildSource::Constant(*val))); - } else { - let z_nr = &e.dynamic_z_child_refs[ref_idx]; - let member_ref = component.mapping.map_property_reference(z_nr, state); - z_sources.push((child_idx as u32, ZChildSource::Property(member_ref))); - ref_idx += 1; + use crate::object_tree::ZOrder; + let mut z_sources: Vec<(u32, ZChildSource)> = Vec::with_capacity(e.children.len()); + for (child_idx, child) in e.children.iter().enumerate() { + let child_z = child.borrow().z_order.clone(); + match child_z { + Some(ZOrder::Constant(val)) => { + z_sources.push((child_idx as u32, ZChildSource::Constant(val))); + } + Some(ZOrder::Dynamic(ref nr)) => { + let member_ref = component.mapping.map_property_reference(nr, state); + z_sources.push((child_idx as u32, ZChildSource::Property(member_ref))); + } + None => { + z_sources.push((child_idx as u32, ZChildSource::Constant(0.0))); + } } } Some(z_sources) diff --git a/internal/compiler/object_tree.rs b/internal/compiler/object_tree.rs index 529fa3f4c2a..1ec360bd937 100644 --- a/internal/compiler/object_tree.rs +++ b/internal/compiler/object_tree.rs @@ -723,6 +723,15 @@ pub struct GeometryProps { pub height: NamedReference, } +/// The z-order of a child element within a parent that has dynamic z-ordering. +#[derive(Clone, Debug)] +pub enum ZOrder { + /// z is a compile-time constant (used for repeater/conditional children). + Constant(f32), + /// z is bound to a runtime expression (NamedReference to the child's z property). + Dynamic(NamedReference), +} + impl GeometryProps { pub fn new(element: &ElementRc) -> Self { Self { @@ -814,12 +823,9 @@ pub struct Element { /// This element's children have dynamic z-ordering (z bound to non-constant expressions) pub has_dynamic_z_order: bool, - /// NamedReferences to non-repeater children's z properties (for dynamic z-ordering). - /// Stored here so they get fixed up by move_declarations. - pub dynamic_z_child_refs: Vec, - /// Compile-time z values for repeater/conditional children (child_index, z_value). - /// These children can't have their z materialized on the parent component. - pub dynamic_z_child_constants: Vec<(usize, f32)>, + /// Per-child z-order info. Set when parent has dynamic z-ordering. + /// Stored on the child so it remains consistent when the children vector is reordered. + pub z_order: Option, pub states: Vec, pub transitions: Vec, @@ -2654,11 +2660,13 @@ pub fn visit_all_named_references_in_element( elem.borrow_mut().geometry_props = Some(geometry_props); } - let mut dynamic_z_child_refs = std::mem::take(&mut elem.borrow_mut().dynamic_z_child_refs); - for r in &mut dynamic_z_child_refs { - vis(r); + let z_order = elem.borrow_mut().z_order.take(); + if let Some(mut zo) = z_order { + if let ZOrder::Dynamic(ref mut nr) = zo { + vis(nr); + } + elem.borrow_mut().z_order = Some(zo); } - elem.borrow_mut().dynamic_z_child_refs = dynamic_z_child_refs; // visit two way bindings for expr in elem.borrow().bindings.values() { diff --git a/internal/compiler/passes/inlining.rs b/internal/compiler/passes/inlining.rs index f8a64f63d29..6cae807dda3 100644 --- a/internal/compiler/passes/inlining.rs +++ b/internal/compiler/passes/inlining.rs @@ -402,8 +402,7 @@ fn duplicate_element_with_mapping( is_flickable_viewport: elem.is_flickable_viewport, has_popup_child: elem.has_popup_child, has_dynamic_z_order: elem.has_dynamic_z_order, - dynamic_z_child_refs: elem.dynamic_z_child_refs.clone(), - dynamic_z_child_constants: elem.dynamic_z_child_constants.clone(), + z_order: elem.z_order.clone(), is_legacy_syntax: elem.is_legacy_syntax, inline_depth: elem.inline_depth + 1, // Deep-clone grid_layout_cell to avoid sharing between original and inlined copies. diff --git a/internal/compiler/passes/repeater_component.rs b/internal/compiler/passes/repeater_component.rs index d61657b2edd..38668585ffd 100644 --- a/internal/compiler/passes/repeater_component.rs +++ b/internal/compiler/passes/repeater_component.rs @@ -60,10 +60,7 @@ fn create_repeater_components(component: &Rc) { is_flickable_viewport: original_elem.is_flickable_viewport, has_popup_child: original_elem.has_popup_child, has_dynamic_z_order: original_elem.has_dynamic_z_order, - dynamic_z_child_refs: std::mem::take(&mut original_elem.dynamic_z_child_refs), - dynamic_z_child_constants: std::mem::take( - &mut original_elem.dynamic_z_child_constants, - ), + z_order: original_elem.z_order.clone(), item_index: Default::default(), // Not determined yet item_index_of_first_children: Default::default(), is_legacy_syntax: original_elem.is_legacy_syntax, diff --git a/internal/compiler/passes/windows.rs b/internal/compiler/passes/windows.rs index 52b49157569..4927d1e02af 100644 --- a/internal/compiler/passes/windows.rs +++ b/internal/compiler/passes/windows.rs @@ -59,8 +59,7 @@ pub fn ensure_window( geometry_props: Default::default(), is_flickable_viewport: false, has_dynamic_z_order: false, - dynamic_z_child_refs: Vec::new(), - dynamic_z_child_constants: Vec::new(), + z_order: None, item_index: Default::default(), item_index_of_first_children: Default::default(), grid_layout_cell: None, diff --git a/internal/compiler/passes/z_order.rs b/internal/compiler/passes/z_order.rs index 4d2249657b0..eb0cbe3f05b 100644 --- a/internal/compiler/passes/z_order.rs +++ b/internal/compiler/passes/z_order.rs @@ -55,17 +55,15 @@ fn reorder_children_by_zorder( .get("z") .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) .unwrap_or(false) + } else if let ElementType::Component(c) = &child_elm.borrow().base_type { + c.root_element + .borrow() + .bindings + .get("z") + .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) + .unwrap_or(false) } else { - if let ElementType::Component(c) = &child_elm.borrow().base_type { - c.root_element - .borrow() - .bindings - .get("z") - .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) - .unwrap_or(false) - } else { - false - } + false }; if !is_const { has_dynamic_z = true; @@ -137,13 +135,11 @@ fn setup_dynamic_z_order( diag: &mut BuildDiagnostics, ) { use crate::namedreference::NamedReference; + use crate::object_tree::ZOrder; elem.borrow_mut().has_dynamic_z_order = true; - let mut z_refs = Vec::new(); - let mut z_constants = Vec::new(); - - for (idx, child_elm) in elem.borrow().children.iter().enumerate() { + for child_elm in elem.borrow().children.iter() { if child_elm.borrow().repeated.is_some() { // Repeater/conditional child: z lives in the inner component. // Must be a compile-time constant; per-item dynamic z in repeaters @@ -166,7 +162,7 @@ fn setup_dynamic_z_order( } else { 0. }; - z_constants.push((idx, z_val as f32)); + child_elm.borrow_mut().z_order = Some(ZOrder::Constant(z_val as f32)); } else { // Non-repeater child: create NamedReference for runtime access. if !child_elm.borrow().bindings.contains_key("z") { @@ -180,12 +176,10 @@ fn setup_dynamic_z_order( .into(), ); } - z_refs.push(NamedReference::new(child_elm, smol_str::SmolStr::new_static("z"))); + let nr = NamedReference::new(child_elm, smol_str::SmolStr::new_static("z")); + child_elm.borrow_mut().z_order = Some(ZOrder::Dynamic(nr)); } } - let mut e = elem.borrow_mut(); - e.dynamic_z_child_refs = z_refs; - e.dynamic_z_child_constants = z_constants; } fn try_eval_const_expr(expression: &Expression) -> Option { diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index d764c8b3341..d24dfe0eac9 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -821,7 +821,8 @@ extern "C" fn visit_children_item( let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; let comp_rc = instance_ref.self_weak().get().unwrap().upgrade().unwrap(); - // Macro-like helper to avoid duplicating the visit_dynamic closure + // Macro needed because `visit_item_tree*` requires the closure to be generic over + // the generativity lifetime of Instance<'_>, which a regular closure cannot satisfy. macro_rules! visit_dyn { () => { |_, order, visitor, index: u32| { @@ -1503,21 +1504,19 @@ pub(crate) fn generate_item_tree<'id>( for (idx, elem_rc) in builder.original_elements.iter().enumerate() { let elem = elem_rc.borrow(); if elem.has_dynamic_z_order { - let mut child_infos = Vec::new(); - let mut ref_idx = 0; - for (child_idx, _child) in elem.children.iter().enumerate() { - if let Some((_, val)) = - elem.dynamic_z_child_constants.iter().find(|(i, _)| *i == child_idx) - { - child_infos.push(ZInterpreterChildInfo::Constant(*val)); - } else if ref_idx < elem.dynamic_z_child_refs.len() { - let nr = &elem.dynamic_z_child_refs[ref_idx]; - child_infos.push(ZInterpreterChildInfo::Property(nr.name().clone())); - ref_idx += 1; - } else { - child_infos.push(ZInterpreterChildInfo::Constant(0.0)); - } - } + let child_infos: Vec<_> = elem + .children + .iter() + .map(|child| match &child.borrow().z_order { + Some(i_slint_compiler::object_tree::ZOrder::Constant(val)) => { + ZInterpreterChildInfo::Constant(*val) + } + Some(i_slint_compiler::object_tree::ZOrder::Dynamic(nr)) => { + ZInterpreterChildInfo::Property(nr.name().clone()) + } + None => ZInterpreterChildInfo::Constant(0.0), + }) + .collect(); z_order_info.insert(idx as u32, child_infos); } } From 2318a39dfe7ffa996ecdec917ae4c99a2b675fc7 Mon Sep 17 00:00:00 2001 From: ruminorix Date: Wed, 6 May 2026 04:28:56 +0000 Subject: [PATCH 4/9] compiler: fix CI warnings in dynamic z-ordering - Remove unused `root` parameter from `visit_tree_z_properties` (clippy::only_used_in_recursion) - Collapse nested `if` into `let` chain in interpreter (clippy::collapsible_if) - Escape `SharedVector` in doc comment (rustdoc unclosed HTML tag) --- .../llr/optim_passes/count_property_use.rs | 12 ++-- internal/core/item_tree.rs | 2 +- internal/interpreter/dynamic_item_tree.rs | 66 +++++++++---------- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/internal/compiler/llr/optim_passes/count_property_use.rs b/internal/compiler/llr/optim_passes/count_property_use.rs index ab7d6f1e318..7c76fb70e7d 100644 --- a/internal/compiler/llr/optim_passes/count_property_use.rs +++ b/internal/compiler/llr/optim_passes/count_property_use.rs @@ -153,11 +153,7 @@ pub fn count_property_use(root: &CompilationUnit) { } // Visit z_sort_order_property references in tree nodes - fn visit_tree_z_properties( - node: &crate::llr::TreeNode, - root: &CompilationUnit, - ctx: &EvaluationContext, - ) { + fn visit_tree_z_properties(node: &crate::llr::TreeNode, ctx: &EvaluationContext) { if let Some(z_props) = &node.z_sort_order_property { for (_, z_source) in z_props { if let crate::llr::ZChildSource::Property(member_ref) = z_source { @@ -166,18 +162,18 @@ pub fn count_property_use(root: &CompilationUnit) { } } for child in &node.children { - visit_tree_z_properties(child, root, ctx); + visit_tree_z_properties(child, ctx); } } for c in &root.public_components { let ctx = EvaluationContext::new_sub_component(root, c.item_tree.root, (), None); - visit_tree_z_properties(&c.item_tree.tree, root, &ctx); + visit_tree_z_properties(&c.item_tree.tree, &ctx); } // Also visit z properties in repeated element sub_trees root.for_each_sub_components(&mut |sc, _ctx| { for r in &sc.repeated { let rep_ctx = EvaluationContext::new_sub_component(root, r.sub_tree.root, (), None); - visit_tree_z_properties(&r.sub_tree.tree, root, &rep_ctx); + visit_tree_z_properties(&r.sub_tree.tree, &rep_ctx); } }); diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index 27c5fe0a3b0..ec1d663e00b 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1399,7 +1399,7 @@ pub fn visit_item_tree( } /// Compute a sorted list of child indices based on their z values. -/// Returns a SharedVector where each entry is a child offset (relative to children_index) +/// Returns a `SharedVector` where each entry is a child offset (relative to children_index) /// sorted by the corresponding z value (stable sort). pub fn compute_sorted_children_by_z(z_values: &[f32]) -> crate::SharedVector { let mut indices: alloc::vec::Vec = (0..z_values.len() as u32).collect(); diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index d24dfe0eac9..cb7b8a59445 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -840,39 +840,39 @@ extern "C" fn visit_children_item( } // Check if this node has dynamic z-ordering - if index >= 0 { - if let Some(z_children) = instance_ref.description.z_order_info.get(&(index as u32)) { - let z_values: Vec = z_children - .iter() - .map(|child_info| match child_info { - ZInterpreterChildInfo::Constant(val) => *val, - ZInterpreterChildInfo::Property(prop_name) => instance_ref - .description - .custom_properties - .get(prop_name) - .and_then(|p| unsafe { - p.prop - .get(core::pin::Pin::new_unchecked( - &*(instance_ref.as_ptr().add(p.offset)), - )) - .ok() - }) - .and_then(|v| if let Value::Number(n) = v { Some(n as f32) } else { None }) - .unwrap_or(0.0), - }) - .collect(); - let sorted = i_slint_core::item_tree::compute_sorted_children_by_z(&z_values); - return i_slint_core::item_tree::visit_item_tree_with_sorted_children( - instance_ref.instance, - &vtable::VRc::into_dyn(comp_rc), - get_item_tree(component).as_slice(), - index, - order, - v, - visit_dyn!(), - sorted.as_slice(), - ); - } + if index >= 0 + && let Some(z_children) = instance_ref.description.z_order_info.get(&(index as u32)) + { + let z_values: Vec = z_children + .iter() + .map(|child_info| match child_info { + ZInterpreterChildInfo::Constant(val) => *val, + ZInterpreterChildInfo::Property(prop_name) => instance_ref + .description + .custom_properties + .get(prop_name) + .and_then(|p| unsafe { + p.prop + .get(core::pin::Pin::new_unchecked( + &*(instance_ref.as_ptr().add(p.offset)), + )) + .ok() + }) + .and_then(|v| if let Value::Number(n) = v { Some(n as f32) } else { None }) + .unwrap_or(0.0), + }) + .collect(); + let sorted = i_slint_core::item_tree::compute_sorted_children_by_z(&z_values); + return i_slint_core::item_tree::visit_item_tree_with_sorted_children( + instance_ref.instance, + &vtable::VRc::into_dyn(comp_rc), + get_item_tree(component).as_slice(), + index, + order, + v, + visit_dyn!(), + sorted.as_slice(), + ); } i_slint_core::item_tree::visit_item_tree( From 6cd4abc4e9b30bf025a7e16c264e941cdb38c90c Mon Sep 17 00:00:00 2001 From: ruminorix Date: Wed, 6 May 2026 06:10:20 +0000 Subject: [PATCH 5/9] compiler: fix ABI mismatch in slint_compute_sorted_children_by_z SharedVector has a non-trivial destructor in C++, causing the C++ ABI to expect the return value via sret (hidden pointer), while Rust returns the pointer-sized repr(C) struct in a register. Use an out-pointer parameter instead, matching the pattern used by other FFI functions. --- internal/compiler/generator/cpp.rs | 3 ++- internal/core/item_tree.rs | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 4a37b45b6b5..6ed2da7bb27 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -1726,7 +1726,8 @@ fn generate_item_tree( let z_list = z_reads.join(", "); visit_children_statements.push(format!("case {node_idx}: {{")); visit_children_statements.push(format!(" float z_values[{count}] = {{{z_list}}};")); - visit_children_statements.push(format!(" auto sorted = slint::cbindgen_private::slint_compute_sorted_children_by_z(slint::cbindgen_private::Slice{{z_values, {count}}});")); + visit_children_statements.push(" slint::SharedVector sorted;".into()); + visit_children_statements.push(format!(" slint::cbindgen_private::slint_compute_sorted_children_by_z(slint::cbindgen_private::Slice{{z_values, {count}}}, &sorted);")); visit_children_statements.push(" return slint::cbindgen_private::slint_visit_item_tree_with_sorted_children(&self_rc, get_item_tree(component), index, order, visitor, dyn_visit, slint::cbindgen_private::Slice{sorted.begin(), sorted.size()});".into()); visit_children_statements.push("}".into()); } diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index ec1d663e00b..b9ed831c081 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1567,8 +1567,11 @@ pub(crate) mod ffi { #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_compute_sorted_children_by_z( z_values: Slice, - ) -> crate::SharedVector { - crate::item_tree::compute_sorted_children_by_z(z_values.as_slice()) + out: *mut crate::SharedVector, + ) { + unsafe { + core::ptr::write(out, crate::item_tree::compute_sorted_children_by_z(z_values.as_slice())) + } } } From 9bd3f6a97663876c4e5b1b206e7fa8dcab651317 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 06:50:22 +0000 Subject: [PATCH 6/9] [autofix.ci] apply automated fixes --- internal/core/item_tree.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index b9ed831c081..6cde4bf5b00 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1570,7 +1570,10 @@ pub(crate) mod ffi { out: *mut crate::SharedVector, ) { unsafe { - core::ptr::write(out, crate::item_tree::compute_sorted_children_by_z(z_values.as_slice())) + core::ptr::write( + out, + crate::item_tree::compute_sorted_children_by_z(z_values.as_slice()), + ) } } } From 9c7b8bca323283c7a6c6cd7f1e8bee74e25c87a7 Mon Sep 17 00:00:00 2001 From: ruminorix Date: Wed, 6 May 2026 07:44:42 +0000 Subject: [PATCH 7/9] core: address review feedback for dynamic z-ordering - Use `u32` indices instead of `f32` for sorted child offsets - Work directly on `SharedVector` to avoid double allocation - Merge `visit_item_tree_with_sorted_children` into `visit_item_tree` with an `Option<&[u32]>` parameter to reduce code duplication - Use `&mut SharedVector` in FFI instead of raw pointer for safety - Add missing skia screenshot reference for zorder_dynamic test --- api/rs/slint/private_unstable_api.rs | 1 - internal/compiler/generator/cpp.rs | 4 +- internal/core/item_tree.rs | 126 ++++++------------ internal/interpreter/dynamic_item_tree.rs | 8 +- .../references/skia/basic/zorder_dynamic.png | Bin 0 -> 391 bytes 5 files changed, 49 insertions(+), 90 deletions(-) create mode 100644 tests/screenshots/references/skia/basic/zorder_dynamic.png diff --git a/api/rs/slint/private_unstable_api.rs b/api/rs/slint/private_unstable_api.rs index e1fa8c4ece7..7e9d79a964d 100644 --- a/api/rs/slint/private_unstable_api.rs +++ b/api/rs/slint/private_unstable_api.rs @@ -189,7 +189,6 @@ pub mod re_exports { pub use i_slint_core::item_tree::{ ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak, TraversalOrder, VisitChildrenResult, compute_sorted_children_by_z, visit_item_tree, - visit_item_tree_with_sorted_children, }; pub use i_slint_core::items::{Transform, *}; pub use i_slint_core::layout::*; diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 6ed2da7bb27..8f69216aa31 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -1726,9 +1726,9 @@ fn generate_item_tree( let z_list = z_reads.join(", "); visit_children_statements.push(format!("case {node_idx}: {{")); visit_children_statements.push(format!(" float z_values[{count}] = {{{z_list}}};")); - visit_children_statements.push(" slint::SharedVector sorted;".into()); + visit_children_statements.push(" slint::SharedVector sorted;".into()); visit_children_statements.push(format!(" slint::cbindgen_private::slint_compute_sorted_children_by_z(slint::cbindgen_private::Slice{{z_values, {count}}}, &sorted);")); - visit_children_statements.push(" return slint::cbindgen_private::slint_visit_item_tree_with_sorted_children(&self_rc, get_item_tree(component), index, order, visitor, dyn_visit, slint::cbindgen_private::Slice{sorted.begin(), sorted.size()});".into()); + visit_children_statements.push(" return slint::cbindgen_private::slint_visit_item_tree_with_sorted_children(&self_rc, get_item_tree(component), index, order, visitor, dyn_visit, slint::cbindgen_private::Slice{sorted.begin(), sorted.size()});".into()); visit_children_statements.push("}".into()); } visit_children_statements.push("default:".into()); diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index 6cde4bf5b00..70991e4e730 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1345,6 +1345,9 @@ fn visit_internal( /// FIXME: the design of this use lots of indirection and stack frame in recursive functions /// Need to check if the compiler is able to optimize away some of it. /// Possibly we should generate code that directly call the visitor instead +/// +/// If `sorted_children_offsets` is `Some`, it provides a pre-computed z-sorted order +/// for children (offsets relative to children_index) instead of the sequential order. pub fn visit_item_tree( base: Pin<&Base>, item_tree: &ItemTreeRc, @@ -1358,6 +1361,7 @@ pub fn visit_item_tree( vtable::VRefMut, u32, ) -> VisitChildrenResult, + sorted_children_offsets: Option<&[u32]>, ) -> VisitChildrenResult { let mut visit_at_index = |idx: u32| -> VisitChildrenResult { match &item_tree_array[idx as usize] { @@ -1381,14 +1385,31 @@ pub fn visit_item_tree( } else { match &item_tree_array[index as usize] { ItemTreeNode::Item { children_index, children_count, .. } => { - for c in 0..*children_count { - let idx = match order { - TraversalOrder::BackToFront => *children_index + c, - TraversalOrder::FrontToBack => *children_index + *children_count - c - 1, - }; - let maybe_abort_index = visit_at_index(idx); - if maybe_abort_index.has_aborted() { - return maybe_abort_index; + if let Some(sorted) = sorted_children_offsets { + let count = sorted.len().min(*children_count as usize); + for i in 0..count { + let offset_idx = match order { + TraversalOrder::BackToFront => i, + TraversalOrder::FrontToBack => count - 1 - i, + }; + let idx = *children_index + sorted[offset_idx]; + let maybe_abort_index = visit_at_index(idx); + if maybe_abort_index.has_aborted() { + return maybe_abort_index; + } + } + } else { + for c in 0..*children_count { + let idx = match order { + TraversalOrder::BackToFront => *children_index + c, + TraversalOrder::FrontToBack => { + *children_index + *children_count - c - 1 + } + }; + let maybe_abort_index = visit_at_index(idx); + if maybe_abort_index.has_aborted() { + return maybe_abort_index; + } } } } @@ -1399,77 +1420,18 @@ pub fn visit_item_tree( } /// Compute a sorted list of child indices based on their z values. -/// Returns a `SharedVector` where each entry is a child offset (relative to children_index) +/// Writes into `out` which will contain child offsets (relative to children_index) /// sorted by the corresponding z value (stable sort). -pub fn compute_sorted_children_by_z(z_values: &[f32]) -> crate::SharedVector { - let mut indices: alloc::vec::Vec = (0..z_values.len() as u32).collect(); - indices.sort_by(|&a, &b| { +pub fn compute_sorted_children_by_z(z_values: &[f32], out: &mut crate::SharedVector) { + out.resize(z_values.len(), 0); + for (i, slot) in out.make_mut_slice().iter_mut().enumerate() { + *slot = i as u32; + } + out.make_mut_slice().sort_by(|&a, &b| { z_values[a as usize] .partial_cmp(&z_values[b as usize]) .unwrap_or(core::cmp::Ordering::Equal) }); - indices.iter().map(|&i| i as f32).collect() -} - -/// Like `visit_item_tree`, but uses a pre-computed sorted order for children instead of -/// the sequential order from the item tree array. -/// `sorted_children_offsets` contains child offsets (0-based relative to children_index) -/// in the desired visitation order (back-to-front). -pub fn visit_item_tree_with_sorted_children( - base: Pin<&Base>, - item_tree: &ItemTreeRc, - item_tree_array: &[ItemTreeNode], - index: isize, - order: TraversalOrder, - mut visitor: vtable::VRefMut, - visit_dynamic: impl Fn( - Pin<&Base>, - TraversalOrder, - vtable::VRefMut, - u32, - ) -> VisitChildrenResult, - sorted_children_offsets: &[f32], -) -> VisitChildrenResult { - let mut visit_at_index = |idx: u32| -> VisitChildrenResult { - match &item_tree_array[idx as usize] { - ItemTreeNode::Item { .. } => { - let item = crate::items::ItemRc::new(item_tree.clone(), idx); - visitor.visit_item(item_tree, idx, item.borrow()) - } - ItemTreeNode::DynamicTree { index, .. } => { - if let Some(sub_idx) = - visit_dynamic(base, order, visitor.borrow_mut(), *index).aborted_index() - { - VisitChildrenResult::abort(idx, sub_idx) - } else { - VisitChildrenResult::CONTINUE - } - } - } - }; - if index == -1 { - visit_at_index(0) - } else { - match &item_tree_array[index as usize] { - ItemTreeNode::Item { children_index, children_count, .. } => { - let count = sorted_children_offsets.len().min(*children_count as usize); - for i in 0..count { - let offset_idx = match order { - TraversalOrder::BackToFront => i, - TraversalOrder::FrontToBack => count - 1 - i, - }; - let child_offset = sorted_children_offsets[offset_idx] as u32; - let idx = *children_index + child_offset; - let maybe_abort_index = visit_at_index(idx); - if maybe_abort_index.has_aborted() { - return maybe_abort_index; - } - } - } - ItemTreeNode::DynamicTree { .. } => panic!("should not be called with dynamic items"), - }; - VisitChildrenResult::CONTINUE - } } #[cfg(feature = "ffi")] @@ -1534,6 +1496,7 @@ pub(crate) mod ffi { order, visitor, |a, b, c, d| visit_dynamic(a.get_ref() as *const vtable::Dyn as *const c_void, b, c, d), + None, ) } @@ -1550,9 +1513,9 @@ pub(crate) mod ffi { visitor: vtable::VRefMut, dyn_index: u32, ) -> VisitChildrenResult, - sorted_children_offsets: Slice, + sorted_children_offsets: Slice, ) -> VisitChildrenResult { - crate::item_tree::visit_item_tree_with_sorted_children( + crate::item_tree::visit_item_tree( VRc::as_pin_ref(item_tree), item_tree, item_tree_array.as_slice(), @@ -1560,21 +1523,16 @@ pub(crate) mod ffi { order, visitor, |a, b, c, d| visit_dynamic(a.get_ref() as *const vtable::Dyn as *const c_void, b, c, d), - sorted_children_offsets.as_slice(), + Some(sorted_children_offsets.as_slice()), ) } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_compute_sorted_children_by_z( z_values: Slice, - out: *mut crate::SharedVector, + out: &mut crate::SharedVector, ) { - unsafe { - core::ptr::write( - out, - crate::item_tree::compute_sorted_children_by_z(z_values.as_slice()), - ) - } + crate::item_tree::compute_sorted_children_by_z(z_values.as_slice(), out); } } diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index cb7b8a59445..44ac40bfba6 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -862,8 +862,9 @@ extern "C" fn visit_children_item( .unwrap_or(0.0), }) .collect(); - let sorted = i_slint_core::item_tree::compute_sorted_children_by_z(&z_values); - return i_slint_core::item_tree::visit_item_tree_with_sorted_children( + let mut sorted = i_slint_core::SharedVector::default(); + i_slint_core::item_tree::compute_sorted_children_by_z(&z_values, &mut sorted); + return i_slint_core::item_tree::visit_item_tree( instance_ref.instance, &vtable::VRc::into_dyn(comp_rc), get_item_tree(component).as_slice(), @@ -871,7 +872,7 @@ extern "C" fn visit_children_item( order, v, visit_dyn!(), - sorted.as_slice(), + Some(sorted.as_slice()), ); } @@ -883,6 +884,7 @@ extern "C" fn visit_children_item( order, v, visit_dyn!(), + None, ) } diff --git a/tests/screenshots/references/skia/basic/zorder_dynamic.png b/tests/screenshots/references/skia/basic/zorder_dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..cbbfd666572ca4686b6e486a9b033990881df825 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVD$5JaSW+oe0zX-flB1GV;}#Q zzw145W_gi!^19sH+n^}3XWlfg_4AC3mEQd<+n!amtlc{M>UySU|Mi>x-(O)f`E0#r z=YLL)fAz{g{`a1$H~u`|drv?}978SN|Al`d|MUBQuXF`Yo?PKCe_?O)IU*nm*;MDp?gZ~?k{8zx{8i> Date: Wed, 6 May 2026 09:04:50 +0000 Subject: [PATCH 8/9] compiler: simplify z-order pass and clean up review findings - Extract `has_z_binding` / `get_z_expr` helpers in z_order.rs to reduce redundant borrows in the first-pass classification loop - Replace silent `.min()` truncation with `debug_assert_eq` in visit_item_tree sorted path (mismatched count is always a bug) - Remove unnecessary "what" comments --- internal/compiler/generator/rust.rs | 1 - .../llr/optim_passes/count_property_use.rs | 2 - internal/compiler/passes/z_order.rs | 65 ++++++++++--------- internal/core/item_tree.rs | 3 +- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 6529db9535e..3b77918c32f 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -1948,7 +1948,6 @@ fn generate_item_tree( })); let mut item_tree_array = Vec::new(); let mut item_array = Vec::new(); - // Collect nodes that have dynamic z-ordering (node_index, sub_component_path, z_sort_order_property) let mut z_sorted_nodes: Vec<( usize, Vec, diff --git a/internal/compiler/llr/optim_passes/count_property_use.rs b/internal/compiler/llr/optim_passes/count_property_use.rs index 7c76fb70e7d..57d00be81bd 100644 --- a/internal/compiler/llr/optim_passes/count_property_use.rs +++ b/internal/compiler/llr/optim_passes/count_property_use.rs @@ -152,7 +152,6 @@ pub fn count_property_use(root: &CompilationUnit) { visit_property(&p.activated, &ctx); } - // Visit z_sort_order_property references in tree nodes fn visit_tree_z_properties(node: &crate::llr::TreeNode, ctx: &EvaluationContext) { if let Some(z_props) = &node.z_sort_order_property { for (_, z_source) in z_props { @@ -169,7 +168,6 @@ pub fn count_property_use(root: &CompilationUnit) { let ctx = EvaluationContext::new_sub_component(root, c.item_tree.root, (), None); visit_tree_z_properties(&c.item_tree.tree, &ctx); } - // Also visit z properties in repeated element sub_trees root.for_each_sub_components(&mut |sc, _ctx| { for r in &sc.repeated { let rep_ctx = EvaluationContext::new_sub_component(root, r.sub_tree.root, (), None); diff --git a/internal/compiler/passes/z_order.rs b/internal/compiler/passes/z_order.rs index eb0cbe3f05b..68c7811b13e 100644 --- a/internal/compiler/passes/z_order.rs +++ b/internal/compiler/passes/z_order.rs @@ -27,45 +27,17 @@ fn reorder_children_by_zorder( elem: &Rc>, diag: &mut BuildDiagnostics, ) { - let children_count = elem.borrow().children.len(); - if children_count == 0 { + if elem.borrow().children.is_empty() { return; } - // First pass: determine if we have any z properties and whether they're all constant let mut has_any_z = false; let mut has_dynamic_z = false; for child_elm in elem.borrow().children.iter() { - let has_z = child_elm.borrow().bindings.contains_key("z"); - let repeated_has_z = if !has_z { - child_elm.borrow().repeated.is_some() - && matches!(&child_elm.borrow().base_type, ElementType::Component(c) - if c.root_element.borrow().bindings.contains_key("z")) - } else { - false - }; - - if has_z || repeated_has_z { + if has_z_binding(child_elm) { has_any_z = true; - let is_const = if has_z { - child_elm - .borrow() - .bindings - .get("z") - .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) - .unwrap_or(false) - } else if let ElementType::Component(c) = &child_elm.borrow().base_type { - c.root_element - .borrow() - .bindings - .get("z") - .map(|e| try_eval_const_expr(&e.borrow().expression).is_some()) - .unwrap_or(false) - } else { - false - }; - if !is_const { + if get_z_expr(child_elm).is_none() { has_dynamic_z = true; } } @@ -182,6 +154,37 @@ fn setup_dynamic_z_order( } } +/// Try to evaluate the z binding expression for a child element, checking both +/// direct bindings and repeated component root elements. +fn get_z_expr(child_elm: &ElementRc) -> Option { + let child = child_elm.borrow(); + if let Some(b) = child.bindings.get("z") { + return try_eval_const_expr(&b.borrow().expression); + } + if child.repeated.is_some() { + if let ElementType::Component(c) = &child.base_type { + if let Some(b) = c.root_element.borrow().bindings.get("z") { + return try_eval_const_expr(&b.borrow().expression); + } + } + } + None +} + +/// Check whether a child element has a z binding at all. +fn has_z_binding(child_elm: &ElementRc) -> bool { + let child = child_elm.borrow(); + if child.bindings.contains_key("z") { + return true; + } + if child.repeated.is_some() { + if let ElementType::Component(c) = &child.base_type { + return c.root_element.borrow().bindings.contains_key("z"); + } + } + false +} + fn try_eval_const_expr(expression: &Expression) -> Option { match super::ignore_debug_hooks(expression) { Expression::NumberLiteral(v, Unit::None) => Some(*v), diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index 70991e4e730..430859af34c 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1386,7 +1386,8 @@ pub fn visit_item_tree( match &item_tree_array[index as usize] { ItemTreeNode::Item { children_index, children_count, .. } => { if let Some(sorted) = sorted_children_offsets { - let count = sorted.len().min(*children_count as usize); + debug_assert_eq!(sorted.len(), *children_count as usize); + let count = sorted.len(); for i in 0..count { let offset_idx = match order { TraversalOrder::BackToFront => i, From f40ea365bde4218bd7b2cb523903485dde1af0a6 Mon Sep 17 00:00:00 2001 From: ruminorix Date: Wed, 6 May 2026 19:37:28 +0000 Subject: [PATCH 9/9] compiler: address review feedback for dynamic z-ordering - Simplify visit_item_tree children loop using map_or to merge the sorted/unsorted branches into one - Remove redundant child index from z_sort_order_property, since entries are always sequential (Vec instead of Vec<(u32, ZChildSource)>) - Visit z properties in popup windows and popup_menu trees in both count_property_use and remove_unused passes - Fix clippy collapsible_if warnings in z_order pass --- internal/compiler/generator/cpp.rs | 4 +- internal/compiler/generator/rust.rs | 4 +- internal/compiler/llr/item_tree.rs | 4 +- internal/compiler/llr/lower_to_item_tree.rs | 10 ++--- .../llr/optim_passes/count_property_use.rs | 18 ++++++++- .../llr/optim_passes/remove_unused.rs | 4 +- internal/compiler/passes/z_order.rs | 19 +++++----- internal/core/item_tree.rs | 37 +++++++------------ 8 files changed, 52 insertions(+), 48 deletions(-) diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 8f69216aa31..66e89789394 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -1597,7 +1597,7 @@ fn generate_item_tree( let mut z_sorted_nodes: Vec<( usize, Vec, - Vec<(u32, llr::ZChildSource)>, + Vec, )> = Vec::new(); let mut current_node_index: usize = 0; @@ -1699,7 +1699,7 @@ fn generate_item_tree( let count = child_z_refs.len(); let z_reads: Vec = child_z_refs .iter() - .map(|(_, z_source)| match z_source { + .map(|z_source| match z_source { llr::ZChildSource::Constant(val) => format!("{val:.1}f"), llr::ZChildSource::Property(member_ref) => match member_ref { llr::MemberReference::Relative { parent_level: 0, local_reference } => { diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 3b77918c32f..6563f883683 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -1951,7 +1951,7 @@ fn generate_item_tree( let mut z_sorted_nodes: Vec<( usize, Vec, - Vec<(u32, llr::ZChildSource)>, + Vec, )> = Vec::new(); let mut current_node_index: usize = 0; sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| { @@ -2070,7 +2070,7 @@ fn generate_item_tree( for (node_idx, node_sub_component_path, child_z_refs) in &z_sorted_nodes { let idx_lit = *node_idx as isize; let count = child_z_refs.len(); - let z_reads: Vec<_> = child_z_refs.iter().map(|(_, z_source)| { + let z_reads: Vec<_> = child_z_refs.iter().map(|z_source| { match z_source { llr::ZChildSource::Constant(val) => { quote!(#val) diff --git a/internal/compiler/llr/item_tree.rs b/internal/compiler/llr/item_tree.rs index 0242f594259..708a516926e 100644 --- a/internal/compiler/llr/item_tree.rs +++ b/internal/compiler/llr/item_tree.rs @@ -397,9 +397,9 @@ pub struct TreeNode { pub children: Vec, pub is_accessible: bool, /// If set, this node's children have dynamic z-ordering. - /// Each entry is (child_offset, source_of_z_value). + /// Each entry corresponds to a child (by index) and describes the source of its z value. /// The code generator will read these z values at runtime and sort children accordingly. - pub z_sort_order_property: Option>, + pub z_sort_order_property: Option>, } /// Source of a child's z value for dynamic z-ordering. diff --git a/internal/compiler/llr/lower_to_item_tree.rs b/internal/compiler/llr/lower_to_item_tree.rs index 1909b4400dd..44c0ef5d0a2 100644 --- a/internal/compiler/llr/lower_to_item_tree.rs +++ b/internal/compiler/llr/lower_to_item_tree.rs @@ -1088,19 +1088,19 @@ fn make_tree( // from each child's z_order field. let z_sort_order_property = if e.has_dynamic_z_order { use crate::object_tree::ZOrder; - let mut z_sources: Vec<(u32, ZChildSource)> = Vec::with_capacity(e.children.len()); - for (child_idx, child) in e.children.iter().enumerate() { + let mut z_sources: Vec = Vec::with_capacity(e.children.len()); + for child in e.children.iter() { let child_z = child.borrow().z_order.clone(); match child_z { Some(ZOrder::Constant(val)) => { - z_sources.push((child_idx as u32, ZChildSource::Constant(val))); + z_sources.push(ZChildSource::Constant(val)); } Some(ZOrder::Dynamic(ref nr)) => { let member_ref = component.mapping.map_property_reference(nr, state); - z_sources.push((child_idx as u32, ZChildSource::Property(member_ref))); + z_sources.push(ZChildSource::Property(member_ref)); } None => { - z_sources.push((child_idx as u32, ZChildSource::Constant(0.0))); + z_sources.push(ZChildSource::Constant(0.0)); } } } diff --git a/internal/compiler/llr/optim_passes/count_property_use.rs b/internal/compiler/llr/optim_passes/count_property_use.rs index 57d00be81bd..0a645460f34 100644 --- a/internal/compiler/llr/optim_passes/count_property_use.rs +++ b/internal/compiler/llr/optim_passes/count_property_use.rs @@ -154,7 +154,7 @@ pub fn count_property_use(root: &CompilationUnit) { fn visit_tree_z_properties(node: &crate::llr::TreeNode, ctx: &EvaluationContext) { if let Some(z_props) = &node.z_sort_order_property { - for (_, z_source) in z_props { + for z_source in z_props { if let crate::llr::ZChildSource::Property(member_ref) = z_source { visit_property(member_ref, ctx); } @@ -168,12 +168,26 @@ pub fn count_property_use(root: &CompilationUnit) { let ctx = EvaluationContext::new_sub_component(root, c.item_tree.root, (), None); visit_tree_z_properties(&c.item_tree.tree, &ctx); } - root.for_each_sub_components(&mut |sc, _ctx| { + root.for_each_sub_components(&mut |sc, ctx| { for r in &sc.repeated { let rep_ctx = EvaluationContext::new_sub_component(root, r.sub_tree.root, (), None); visit_tree_z_properties(&r.sub_tree.tree, &rep_ctx); } + for popup in &sc.popup_windows { + let parent_ctx = ParentScope::new(ctx, None); + let popup_ctx = EvaluationContext::new_sub_component( + root, + popup.item_tree.root, + (), + Some(&parent_ctx), + ); + visit_tree_z_properties(&popup.item_tree.tree, &popup_ctx); + } }); + if let Some(p) = &root.popup_menu { + let ctx = EvaluationContext::new_sub_component(root, p.item_tree.root, (), None); + visit_tree_z_properties(&p.item_tree.tree, &ctx); + } clean_unused_bindings(root); } diff --git a/internal/compiler/llr/optim_passes/remove_unused.rs b/internal/compiler/llr/optim_passes/remove_unused.rs index 87020446d70..2b7a7ba3695 100644 --- a/internal/compiler/llr/optim_passes/remove_unused.rs +++ b/internal/compiler/llr/optim_passes/remove_unused.rs @@ -287,7 +287,7 @@ mod visitor { visitor: &mut (impl Visitor + ?Sized), ) { if let Some(z_props) = &mut node.z_sort_order_property { - for (_, z_source) in z_props { + for z_source in z_props { if let crate::llr::ZChildSource::Property(member_ref) = z_source { visit_member_reference(member_ref, scope, state, visitor); } @@ -374,6 +374,7 @@ mod visitor { for p in popup_windows { let popup_scope = EvaluationScope::SubComponent(p.item_tree.root, None); visit_expression(p.position.get_mut(), &popup_scope, state, visitor); + visit_tree_node_z_properties(&mut p.item_tree.tree, &popup_scope, state, visitor); } for t in timers { visit_expression(t.interval.get_mut(), &scope, state, visitor); @@ -495,6 +496,7 @@ mod visitor { visit_member_reference(activated, &scope, state, visitor); visit_member_reference(close, &scope, state, visitor); visit_member_reference(entries, &scope, state, visitor); + visit_tree_node_z_properties(&mut item_tree.tree, &scope, state, visitor); } pub fn visit_public_property( diff --git a/internal/compiler/passes/z_order.rs b/internal/compiler/passes/z_order.rs index 68c7811b13e..2ef7c7dbef4 100644 --- a/internal/compiler/passes/z_order.rs +++ b/internal/compiler/passes/z_order.rs @@ -161,12 +161,11 @@ fn get_z_expr(child_elm: &ElementRc) -> Option { if let Some(b) = child.bindings.get("z") { return try_eval_const_expr(&b.borrow().expression); } - if child.repeated.is_some() { - if let ElementType::Component(c) = &child.base_type { - if let Some(b) = c.root_element.borrow().bindings.get("z") { - return try_eval_const_expr(&b.borrow().expression); - } - } + if child.repeated.is_some() + && let ElementType::Component(c) = &child.base_type + && let Some(b) = c.root_element.borrow().bindings.get("z") + { + return try_eval_const_expr(&b.borrow().expression); } None } @@ -177,10 +176,10 @@ fn has_z_binding(child_elm: &ElementRc) -> bool { if child.bindings.contains_key("z") { return true; } - if child.repeated.is_some() { - if let ElementType::Component(c) = &child.base_type { - return c.root_element.borrow().bindings.contains_key("z"); - } + if child.repeated.is_some() + && let ElementType::Component(c) = &child.base_type + { + return c.root_element.borrow().bindings.contains_key("z"); } false } diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index 430859af34c..646ef278d00 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -1387,30 +1387,19 @@ pub fn visit_item_tree( ItemTreeNode::Item { children_index, children_count, .. } => { if let Some(sorted) = sorted_children_offsets { debug_assert_eq!(sorted.len(), *children_count as usize); - let count = sorted.len(); - for i in 0..count { - let offset_idx = match order { - TraversalOrder::BackToFront => i, - TraversalOrder::FrontToBack => count - 1 - i, - }; - let idx = *children_index + sorted[offset_idx]; - let maybe_abort_index = visit_at_index(idx); - if maybe_abort_index.has_aborted() { - return maybe_abort_index; - } - } - } else { - for c in 0..*children_count { - let idx = match order { - TraversalOrder::BackToFront => *children_index + c, - TraversalOrder::FrontToBack => { - *children_index + *children_count - c - 1 - } - }; - let maybe_abort_index = visit_at_index(idx); - if maybe_abort_index.has_aborted() { - return maybe_abort_index; - } + } + let count = *children_count as usize; + for i in 0..count { + let offset_idx = match order { + TraversalOrder::BackToFront => i, + TraversalOrder::FrontToBack => count - 1 - i, + }; + let idx = *children_index + + sorted_children_offsets + .map_or(offset_idx as u32, |sorted| sorted[offset_idx]); + let maybe_abort_index = visit_at_index(idx); + if maybe_abort_index.has_aborted() { + return maybe_abort_index; } } }