Skip to main content

slint_interpreter/
instance.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Runtime component tree: a hierarchy of [`SubComponentInstance`]s rooted
5//! in an [`Instance`].
6
7use crate::erased::{ErasedItemRc, SubComponentCallback, SubComponentProperty};
8use crate::globals::GlobalStorage;
9use crate::item_registry::ItemRegistry;
10use i_slint_compiler::llr::{
11    self, CompilationUnit, ItemInstanceIdx, RepeatedElementIdx, SubComponentIdx,
12    SubComponentInstanceIdx,
13};
14use i_slint_core::item_tree::{ItemTreeNode, ItemTreeVTable};
15use i_slint_core::model::{Conditional, Repeater};
16use i_slint_core::properties::ChangeTracker;
17use i_slint_core::window::WindowAdapterRc;
18use i_slint_core::{Callback, Property};
19use std::cell::{OnceCell, RefCell};
20use std::pin::Pin;
21use std::rc::{Rc, Weak};
22use typed_index_collections::TiVec;
23use vtable::{VRc, VWeak};
24
25/// Either a `Repeater<Instance>` (`for` loops) or a `Conditional<Instance>`
26/// (`if expr` elements).
27/// The conditional variant reuses the existing instance while the condition
28/// stays true, avoiding spurious re-init.
29pub enum RepeaterOrConditional {
30    Repeater(Pin<Box<Repeater<Instance>>>),
31    Conditional(Pin<Box<Conditional<Instance>>>),
32}
33
34impl RepeaterOrConditional {
35    pub fn visit(
36        &self,
37        order: i_slint_core::item_tree::TraversalOrder,
38        visitor: i_slint_core::item_tree::ItemVisitorRefMut<'_>,
39    ) -> i_slint_core::item_tree::VisitChildrenResult {
40        match self {
41            Self::Repeater(r) => Pin::as_ref(r).visit(order, visitor),
42            Self::Conditional(c) => Pin::as_ref(c).visit(order, visitor),
43        }
44    }
45
46    pub fn visit_maybe_instance(
47        &self,
48        instance: Option<u32>,
49        order: i_slint_core::item_tree::TraversalOrder,
50        visitor: i_slint_core::item_tree::ItemVisitorRefMut<'_>,
51    ) -> i_slint_core::item_tree::VisitChildrenResult {
52        match self {
53            Self::Repeater(r) => Pin::as_ref(r).visit_maybe_instance(instance, order, visitor),
54            Self::Conditional(c) => Pin::as_ref(c).visit_maybe_instance(instance, order, visitor),
55        }
56    }
57
58    /// Call `cb` with the index and z value of every instance, for a repeated or
59    /// conditional element whose z value is dynamic.
60    pub fn for_each_instance_z(&self, cb: &mut dyn FnMut(u32, f32)) {
61        match self {
62            Self::Repeater(r) => Pin::as_ref(r).for_each_instance_z(cb),
63            Self::Conditional(c) => Pin::as_ref(c).for_each_instance_z(cb),
64        }
65    }
66
67    pub fn range(&self) -> core::ops::Range<usize> {
68        match self {
69            Self::Repeater(r) => r.range(),
70            Self::Conditional(c) => c.range(),
71        }
72    }
73
74    pub fn instance_at(&self, subindex: usize) -> Option<VRc<ItemTreeVTable, Instance>> {
75        match self {
76            Self::Repeater(r) => r.instance_at(subindex),
77            Self::Conditional(c) => c.instance_at(subindex),
78        }
79    }
80
81    pub fn instances_vec(&self) -> Vec<VRc<ItemTreeVTable, Instance>> {
82        match self {
83            Self::Repeater(r) => r.instances_vec(),
84            Self::Conditional(c) => c.instances_vec(),
85        }
86    }
87
88    /// Register the instance generation as a dependency of the current
89    /// tracking scope. Layout expressions use this instead of instantiating,
90    /// so they re-evaluate after the `ensure_instantiated` pass materializes
91    /// instance changes.
92    pub fn track_instance_changes(&self) {
93        match self {
94            Self::Repeater(r) => Pin::as_ref(r).track_instance_changes(),
95            Self::Conditional(c) => Pin::as_ref(c).track_instance_changes(),
96        }
97    }
98
99    /// Ensure the repeater/conditional has been updated. Must be called
100    /// before accessing instances.
101    /// Returns `true` if instances were created or removed.
102    pub fn ensure_updated(
103        &self,
104        init: impl Fn() -> VRc<ItemTreeVTable, Instance> + 'static,
105    ) -> bool {
106        match self {
107            Self::Repeater(r) => Pin::as_ref(r).ensure_updated(init),
108            Self::Conditional(c) => Pin::as_ref(c).ensure_updated(init),
109        }
110    }
111
112    /// Like `ensure_updated` but for listview repeaters that need
113    /// virtualized row layout. The interpreter's content properties may
114    /// live on a native item (e.g. `Flickable::content-y`), which
115    /// doesn't expose a `Pin<&Property<Value>>` — so we go through the
116    /// closure-based [`i_slint_core::model::ListViewProperties`] variant
117    /// and let `load_property`/`store_property` route to rtti as needed.
118    pub fn ensure_updated_listview_callback(
119        &self,
120        init: impl Fn() -> VRc<ItemTreeVTable, Instance> + 'static,
121        props: &dyn i_slint_core::model::ListViewProperties,
122        listview_width: i_slint_core::lengths::LogicalLength,
123        listview_height: i_slint_core::lengths::LogicalLength,
124    ) -> bool {
125        match self {
126            Self::Repeater(r) => Pin::as_ref(r).ensure_updated_listview_callback(
127                init,
128                props,
129                listview_width,
130                listview_height,
131            ),
132            Self::Conditional(_) => unreachable!("listview on a conditional element"),
133        }
134    }
135
136    /// Set the model binding for `for` repeaters.
137    pub fn set_model_binding(
138        &self,
139        binding: impl Fn() -> i_slint_core::model::ModelRc<crate::Value> + 'static,
140    ) {
141        match self {
142            Self::Repeater(r) => Pin::as_ref(r).set_model_binding(binding),
143            Self::Conditional(_) => unreachable!("set_model_binding on conditional"),
144        }
145    }
146
147    /// Set the condition binding for conditional elements.
148    pub fn set_condition_binding(&self, binding: impl Fn() -> bool + 'static) {
149        match self {
150            Self::Conditional(c) => c.set_model_binding(binding),
151            Self::Repeater(_) => unreachable!("set_condition_binding on repeater"),
152        }
153    }
154
155    /// Write model data back to a for-loop model row.
156    pub fn model_set_row_data(&self, row: usize, data: crate::Value) {
157        match self {
158            Self::Repeater(r) => Pin::as_ref(r).model_set_row_data(row, data),
159            Self::Conditional(_) => {} // conditionals have no model data
160        }
161    }
162
163    pub fn is_conditional(&self) -> bool {
164        matches!(self, Self::Conditional(_))
165    }
166}
167
168/// Runtime instance of a single [`SubComponent`](llr::SubComponent).
169///
170/// Each field is indexed by its corresponding LLR index, so lookups are O(1).
171pub struct SubComponentInstance {
172    pub compilation_unit: Rc<CompilationUnit>,
173    pub sub_component_idx: SubComponentIdx,
174    pub properties: TiVec<llr::PropertyIdx, SubComponentProperty>,
175    pub callbacks: TiVec<llr::CallbackIdx, SubComponentCallback>,
176    /// For each callback with `needs_tracker`, a `Property<()>` that tracks
177    /// handler changes: invoking the callback from a binding reads it to
178    /// register a dependency; setting a new handler marks it dirty so
179    /// dependent bindings re-evaluate.
180    pub callback_trackers: TiVec<llr::CallbackIdx, Option<Pin<Rc<Property<()>>>>>,
181    pub items: TiVec<ItemInstanceIdx, ErasedItemRc>,
182    pub sub_components: TiVec<SubComponentInstanceIdx, Pin<Rc<SubComponentInstance>>>,
183    /// One repeater per LLR `RepeatedElementIdx`.
184    /// Conditional elements (`if expr`) use `Conditional<Instance>` which
185    /// reuses the existing instance when the condition stays true; `for`
186    /// loops use `Repeater<Instance>` which manages a `ModelRc<Value>`.
187    pub repeaters: TiVec<RepeatedElementIdx, RepeaterOrConditional>,
188    /// Resolves `MemberReference::Relative { parent_level: > 0 }`.
189    pub parent: Weak<SubComponentInstance>,
190    /// Back-reference to the owning root, populated right after construction.
191    pub root: OnceCell<VWeak<ItemTreeVTable, Instance>>,
192    /// Change trackers for the timers (two per timer, first) and the
193    /// `change_callbacks` (in declaration order, after).
194    pub change_trackers: Vec<ChangeTracker>,
195    /// Per-sub-component runtime `Timer`s, one per `SubComponent::timers`
196    /// entry. Owned here so they stay alive with the instance; their
197    /// lifecycle (start / stop / interval) is driven by a change tracker
198    /// that re-evaluates the LLR `running` / `interval` expressions.
199    pub timers: Vec<i_slint_core::timers::Timer>,
200    /// One entry per `SubComponent::popup_windows`. Stores the currently
201    /// open popup's id (handed out by `WindowInner::show_popup`) so a
202    /// later `popup.close()` in the same sub-component can resolve which
203    /// popup to tear down.
204    pub popup_ids: Vec<std::cell::Cell<Option<std::num::NonZeroU32>>>,
205    /// Set on the root sub-component of a repeated `Instance`. Points back to
206    /// the parent sub-component holding the `Repeater` this instance belongs to.
207    /// Used by `ModelDataAssignment` to write back into the model.
208    pub repeated_in: OnceCell<(Weak<SubComponentInstance>, RepeatedElementIdx)>,
209    /// Keeps the `MenuFromItemTree` alive so the weak reference stored by
210    /// `setup_menubar_shortcuts` in the window remains valid.
211    pub menubar: RefCell<Option<vtable::VRc<i_slint_core::menus::MenuVTable>>>,
212}
213
214/// Top-level item tree handed to i-slint-core via `VRc<ItemTreeVTable, _>`.
215pub struct Instance {
216    pub root_sub_component: Pin<Rc<SubComponentInstance>>,
217    /// Flat `ItemTreeNode` slice returned by the `get_item_tree` vtable entry.
218    pub tree_nodes: Box<[ItemTreeNode]>,
219    /// Parallel table mapping each `DynamicTree` flat index to the
220    /// `(sub_component_path, RepeatedElementIdx)` that owns the repeater.
221    /// `None` entries correspond to non-dynamic nodes.
222    pub dynamic_table: Box<[Option<(Box<[SubComponentInstanceIdx]>, RepeatedElementIdx)>]>,
223    /// Parallel table mapping each static-item flat index to the
224    /// `(sub_component_path, ItemInstanceIdx)` that owns it. `None`
225    /// entries correspond to dynamic-tree nodes.
226    pub item_table: Box<[Option<(Box<[SubComponentInstanceIdx]>, ItemInstanceIdx)>]>,
227    /// Parallel table mapping each flat tree index whose children are dynamically
228    /// z-ordered to the per-child z sources. `None` for every other node.
229    pub z_sort_table: Box<[Option<Vec<llr::ZSource>>]>,
230    pub globals: Rc<GlobalStorage>,
231    pub self_weak: OnceCell<VWeak<ItemTreeVTable, Instance>>,
232    /// When this `Instance` is a repeated entry, points back to the parent
233    /// item tree so `parent_node` can return a meaningful weak.
234    pub parent_instance: Weak<SubComponentInstance>,
235    /// Index into `compilation_unit.public_components` for the public
236    /// component this instance was built from. `None` for repeated /
237    /// nested instances that don't correspond to a public component.
238    pub public_component_index: Option<usize>,
239    /// Lazily-created window adapter, used by `ImplicitLayoutInfo` and the
240    /// public window/run helpers.
241    pub window_adapter: OnceCell<WindowAdapterRc>,
242    /// Message of the first failed window adapter creation. Later accesses
243    /// return it instead of asking the platform again, so the first error is
244    /// what `create()` reports.
245    window_adapter_error: OnceCell<String>,
246    /// Set once [`Instance::attach_to_window`] has linked the window adapter
247    /// back to this item tree via `WindowInner::set_component`. Keeps the
248    /// attach idempotent and lets binding-evaluated code paths distinguish
249    /// "adapter exists" from "window is fully wired for display".
250    pub window_attached: OnceCell<()>,
251    /// Set once `bindings::install_bindings_only` has wired up property
252    /// bindings, two-way links and timers. Idempotent on repeated calls.
253    pub bindings_installed: OnceCell<()>,
254    /// Set once the user-facing `init_code` has run on this instance. Kept
255    /// separate from `bindings_installed` so the listview-virtualization
256    /// factory can install bindings eagerly (so the first measurement
257    /// returns the right row height) while still deferring `init_code`
258    /// until the core's `init_instances` step.
259    pub init_code_run: OnceCell<()>,
260    /// When this instance has been embedded into another item tree via
261    /// `embed_component`, stores the weak handle to the outer item tree and
262    /// the flat index of the `ComponentContainer` it substitutes into.
263    /// `parent_node` uses this to let coordinate-mapping helpers walk up
264    /// into the outer tree.
265    pub embedded_in: OnceCell<(VWeak<ItemTreeVTable>, u32)>,
266    /// `TypeLoader` snapshots (post-pass + pre-pass) kept around for the
267    /// highlight module and the LSP live preview's `DocumentCache`
268    /// reconstruction. Both sides are `None` on sub-tree / popup / repeated
269    /// instances — only the top-level definition sets them.
270    pub type_loaders: crate::component::TypeLoaders,
271}
272
273impl Drop for Instance {
274    fn drop(&mut self) {
275        // Free the per-component renderer caches (text shaping, bounding rects, …)
276        // and notify any `WindowAdapterInternal` that the item tree is
277        // going away. Skipping this leaks cache entries across destroyed
278        // conditional/repeated sub-trees; once the allocator hands out a
279        // fresh item at a previously-cached pointer, the renderer serves
280        // the old widget's text / font / color.
281        //
282        // `self_weak` can't be upgraded here — the strong count is already
283        // zero — so build a borrowed `VRef<ItemTreeVTable>` from `&*self`.
284        let Some(adapter) = self.window_adapter.get().cloned().or_else(|| {
285            let mut parent = self.parent_instance.upgrade();
286            while let Some(sub) = parent {
287                let root = sub.root.get().and_then(|w| w.upgrade())?;
288                if let Some(a) = root.window_adapter.get() {
289                    return Some(a.clone());
290                }
291                parent = root.parent_instance.upgrade();
292            }
293            None
294        }) else {
295            return;
296        };
297        vtable::new_vref!(let item_tree_ref : VRef<i_slint_core::item_tree::ItemTreeVTable> for i_slint_core::item_tree::ItemTree = self);
298        let items = collect_item_refs(&self.root_sub_component);
299        // Same order as `i_slint_core::item_tree::unregister_item_tree`:
300        // deinit each item (a focused TextInput resets
301        // `text-input-focused`), free the renderer caches, notify the
302        // adapter, then close popups whose parent item just went away.
303        for item in &items {
304            item.as_ref().deinit(&adapter);
305        }
306        let _ =
307            adapter.renderer().free_graphics_resources(item_tree_ref, &mut items.iter().copied());
308        if let Some(internal) = adapter.internal(i_slint_core::InternalToken) {
309            internal.unregister_item_tree(item_tree_ref, &mut items.iter().copied());
310        }
311        let window_inner = i_slint_core::window::WindowInner::from_pub(adapter.window());
312        let to_close_popups = window_inner
313            .active_popups()
314            .iter()
315            .filter_map(|p| p.parent_item.upgrade().is_none().then_some(p.popup_id))
316            .collect::<Vec<_>>();
317        for popup_id in to_close_popups {
318            window_inner.close_popup(popup_id);
319        }
320    }
321}
322
323/// Collect every native item in `sub` and its nested sub-components as
324/// pinned vtable refs for `free_graphics_resources` / `unregister_item_tree`.
325fn collect_item_refs<'a>(
326    sub: &'a Pin<Rc<SubComponentInstance>>,
327) -> Vec<Pin<vtable::VRef<'a, i_slint_core::items::ItemVTable>>> {
328    let mut out = Vec::new();
329    fn walk<'a>(
330        sub: &'a Pin<Rc<SubComponentInstance>>,
331        out: &mut Vec<Pin<vtable::VRef<'a, i_slint_core::items::ItemVTable>>>,
332    ) {
333        for item in &sub.items {
334            out.push(Pin::as_ref(item).as_item_ref());
335        }
336        for nested in &sub.sub_components {
337            walk(nested, out);
338        }
339    }
340    walk(sub, &mut out);
341    out
342}
343
344impl Instance {
345    /// Like [`Self::try_window_adapter`], but collapse the error case to
346    /// `None` for the many callers that only need best-effort access.
347    pub fn window_adapter_or_default(&self) -> Option<WindowAdapterRc> {
348        self.try_window_adapter().ok()
349    }
350
351    /// Return a window adapter, creating one through the platform selector
352    /// if needed. Failure to create one surfaces as the platform's error so
353    /// callers with an error channel (e.g. `create()`) can report it.
354    ///
355    /// Does **not** call `WindowInner::set_component`: this method is called
356    /// from inside binding evaluation (e.g. `ImplicitLayoutInfo`), and
357    /// `set_component` eagerly reads and writes window-item properties,
358    /// which would recurse into the in-flight binding. Call
359    /// [`Self::attach_to_window`] separately from lifecycle entry points
360    /// (show/run) to link the window back to this item tree.
361    ///
362    /// Sub-instances (popups, repeated/conditional sub-trees) inherit the
363    /// adapter of the root instance instead of creating a fresh one — that
364    /// would otherwise leave dispatched events going to a different window
365    /// than the one the test driver captured.
366    pub fn try_window_adapter(&self) -> Result<WindowAdapterRc, i_slint_core::api::PlatformError> {
367        if let Some(a) = self.window_adapter.get() {
368            return Ok(a.clone());
369        }
370        // An embedded instance reuses the outer tree's adapter. We must
371        // _not_ create a fresh one: any resize event on it would fire
372        // `set_window_item_geometry`, which walks the TwoWayBinding chain
373        // down into `common_1.set(..)` and erases the ComponentContainer
374        // width/height bindings the embedded root is supposed to track.
375        if let Some((outer_weak, _)) = self.embedded_in.get()
376            && let Some(outer) = outer_weak.upgrade()
377        {
378            let mut result = None;
379            vtable::VRc::borrow_pin(&outer).as_ref().window_adapter(true, &mut result);
380            if let Some(a) = result {
381                let _ = self.window_adapter.set(a.clone());
382                return Ok(a);
383            }
384        }
385        // Walk up the parent chain to find an existing adapter on the root
386        // instance, so popup-in-popup etc. share the same window.
387        let mut outermost_root = None;
388        let mut parent_sub = self.parent_instance.upgrade();
389        while let Some(sub) = parent_sub {
390            let Some(root_vrc) = sub.root.get().and_then(|w| w.upgrade()) else { break };
391            if let Some(a) = root_vrc.window_adapter.get() {
392                let cloned = a.clone();
393                // Cache on this instance so future lookups don't have to walk
394                // again, but don't store a *new* adapter on a non-root.
395                let _ = self.window_adapter.set(cloned.clone());
396                return Ok(cloned);
397            }
398            parent_sub = root_vrc.parent_instance.upgrade();
399            outermost_root = Some(root_vrc);
400        }
401        if let Some(e) = self
402            .window_adapter_error
403            .get()
404            .or_else(|| outermost_root.as_ref().and_then(|root| root.window_adapter_error.get()))
405        {
406            return Err(i_slint_core::api::PlatformError::Other(e.clone()));
407        }
408        let adapter = i_slint_backend_selector::with_platform(|p| p.create_window_adapter())
409            .inspect_err(|e| {
410                let msg = e.to_string();
411                if let Some(root) = &outermost_root {
412                    let _ = root.window_adapter_error.set(msg.clone());
413                }
414                let _ = self.window_adapter_error.set(msg);
415            })?;
416        // Point the renderer at its adapter right away: font registration in
417        // `pre_init_code` and image decoding need the renderer's Slint context
418        // before `attach_to_window` runs `set_component` on show.
419        adapter.renderer().set_window_adapter(&adapter);
420        // A freshly created adapter belongs to the outermost root instance;
421        // caching it only on a sub-tree would leave the root creating a
422        // second one later, splitting the tree across two windows.
423        if let Some(root) = outermost_root {
424            let _ = root.window_adapter.set(adapter.clone());
425        }
426        let _ = self.window_adapter.set(adapter.clone());
427        Ok(adapter)
428    }
429
430    /// Link this instance's root item tree into its window adapter via
431    /// `WindowInner::set_component`, if not already attached.
432    ///
433    /// Must be called from a context that is **not** currently evaluating a
434    /// property binding — `set_component` touches geometry and scale-factor
435    /// trackers and would otherwise trip `Recursion detected`. The public
436    /// `show()` / `run()` entry points call this before handing off to the
437    /// backend event loop. Idempotent via the `window_attached` flag.
438    pub fn attach_to_window(&self) {
439        // make sure not to attach embedded instances, they would otherwise take over
440        // the window of the item tree they are embedded in.
441        if self.window_attached.get().is_some() || self.embedded_in.get().is_some() {
442            return;
443        }
444        let Some(adapter) = self.window_adapter_or_default() else { return };
445        let Some(self_rc) = self.self_weak.get().and_then(|w| w.upgrade()) else { return };
446        let _ = self.window_attached.set(());
447        i_slint_core::window::WindowInner::from_pub(adapter.window())
448            .set_component(&vtable::VRc::into_dyn(self_rc));
449    }
450}
451
452/// When the LLR `RepeatedElement` at `rep_idx` is actually a
453/// `ComponentContainer` placeholder (created by `lower_component_container`),
454/// return a pinned reference to the `ComponentContainer` item that hosts
455/// the embedded tree. Returns `None` for regular repeaters and conditional
456/// elements.
457pub(crate) fn component_container_item(
458    sub: &Pin<Rc<SubComponentInstance>>,
459    rep_idx: RepeatedElementIdx,
460) -> Option<Pin<&i_slint_core::items::ComponentContainer>> {
461    let sc = &sub.compilation_unit.sub_components[sub.sub_component_idx];
462    let cc_item_idx = sc.repeated.get(rep_idx)?.container_item_index?;
463    let item = sub.items.get(cc_item_idx)?;
464    i_slint_core::items::ItemRef::downcast_pin::<i_slint_core::items::ComponentContainer>(
465        Pin::as_ref(item).as_item_ref(),
466    )
467}
468
469impl Instance {
470    /// Resolve a flat `tree_nodes` index into the owning sub-component and
471    /// its local repeater index by walking the cached
472    /// `dynamic_table` entry's `sub_component_path`.
473    pub fn dynamic_at(
474        &self,
475        tree_index: u32,
476    ) -> Option<(Pin<Rc<SubComponentInstance>>, RepeatedElementIdx)> {
477        let entry = self.dynamic_table.get(tree_index as usize)?.as_ref()?;
478        let mut current = self.root_sub_component.clone();
479        for &idx in entry.0.iter() {
480            let next = current.sub_components[idx].clone();
481            current = next;
482        }
483        Some((current, entry.1))
484    }
485
486    /// Ensure the repeater at `tree_index` is populated from its model.
487    /// Called by `get_subtree_range`, `get_subtree` and
488    /// `visit_dynamic_children` before reading the repeater's instances.
489    ///
490    /// When the LLR `RepeatedElement` is actually a `ComponentContainer`
491    /// placeholder (`container_item_index = Some`), defer to the
492    /// `ComponentContainer` item's own `ensure_updated`, which drives
493    /// the `ComponentFactory` and stores the embedded item tree on the
494    /// container item directly — the repeater slot stays a no-op
495    /// `Conditional` with `model: false`.
496    pub fn ensure_updated(&self, tree_index: u32) -> bool {
497        let Some((sub, rep_idx)) = self.dynamic_at(tree_index) else { return false };
498        if let Some(cc) = component_container_item(&sub, rep_idx) {
499            return cc.ensure_updated();
500        }
501        let cu = sub.compilation_unit.clone();
502        let sc_idx = sub.sub_component_idx;
503        let sub_weak = Rc::downgrade(&Pin::into_inner(sub.clone()));
504        let globals = self.globals.clone();
505        let repeated = &cu.sub_components[sc_idx].repeated[rep_idx];
506        let listview_factory = repeated.listview.is_some();
507        let listview_info = repeated.listview.clone();
508        let factory = move || {
509            let item_tree = &cu.sub_components[sc_idx].repeated[rep_idx].sub_tree;
510            let vrc = Instance::new_repeated(
511                cu.clone(),
512                item_tree,
513                sub_weak.clone(),
514                rep_idx,
515                globals.clone(),
516            );
517            if listview_factory {
518                // The listview measurement reads row heights *before* the
519                // core calls `RepeatedItemTree::init` on each row, so the
520                // height/width/geometry bindings must be in place
521                // immediately; `init_code` stays deferred to `init()`.
522                install_bindings_for_repeated_row(&vrc);
523            }
524            vrc
525        };
526        let repeater = &sub.repeaters[rep_idx];
527        if let Some(lv) = listview_info.as_ref() {
528            let listview_width = read_logical_length(&sub, &lv.listview_width);
529            let listview_height = read_logical_length(&sub, &lv.listview_height);
530            // If layout hasn't propagated a real visible height yet (eager
531            // hit-test before show()), bail out instead of running the
532            // virtualization with `0`, which would create no rows or — with
533            // the loop_count == 3 retry — instantiate the whole model.
534            if listview_height.get() <= 0.0 {
535                return false;
536            }
537            let props = ValueListViewProps {
538                content_y: lv.content_y.clone(),
539                content_width: lv.content_width.clone(),
540                content_height: lv.content_height.clone(),
541                ctx_sub: sub.clone(),
542            };
543            repeater.ensure_updated_listview_callback(
544                factory,
545                &props,
546                listview_width,
547                listview_height,
548            )
549        } else {
550            repeater.ensure_updated(factory)
551        }
552    }
553
554    /// Instantiate every repeater, conditional and `ComponentContainer` in
555    /// this item tree. Runs as a dedicated update pass before rendering and
556    /// event dispatch, so the visit pass only has to register dependencies.
557    /// Returns `true` if any instance was created or removed.
558    pub fn ensure_instantiated(&self) -> bool {
559        let mut changed = false;
560        for idx in 0..self.dynamic_table.len() {
561            if self.dynamic_table[idx].is_some() {
562                changed |= self.ensure_updated(idx as u32);
563            }
564        }
565        changed
566    }
567
568    /// `visit_children_item` entry point for `DynamicTree` nodes.
569    ///
570    /// For `ComponentContainer` placeholders the visit delegates to the
571    /// container item's own `visit_children_item`, which hops into the
572    /// embedded item tree stored on the container. The repeater slot is
573    /// a dummy `Conditional` (see `lower_component_container`) and must
574    /// not be visited directly, or the embedded content never renders.
575    pub fn visit_dynamic_children(
576        self: Pin<&Self>,
577        dyn_index: u32,
578        order: i_slint_core::item_tree::TraversalOrder,
579        visitor: vtable::VRefMut<'_, i_slint_core::item_tree::ItemVisitorVTable>,
580        instance: Option<u32>,
581    ) -> i_slint_core::item_tree::VisitChildrenResult {
582        let Some((sub, rep_idx)) = self.get_ref().dynamic_at(dyn_index) else {
583            return i_slint_core::item_tree::VisitChildrenResult::CONTINUE;
584        };
585        if let Some(cc) = component_container_item(&sub, rep_idx) {
586            return cc.visit_children_item(-1, order, visitor);
587        }
588        // Instantiation happens in the `ensure_instantiated` pass; the visit
589        // only registers dependencies so the redraw tracker is notified when
590        // the model or the ListView content geometry changes.
591        let repeater = &sub.repeaters[rep_idx];
592        let sc = &sub.compilation_unit.sub_components[sub.sub_component_idx];
593        if let (Some(lv), RepeaterOrConditional::Repeater(r)) =
594            (sc.repeated[rep_idx].listview.as_ref(), repeater)
595        {
596            let props = ValueListViewProps {
597                content_y: lv.content_y.clone(),
598                content_width: lv.content_width.clone(),
599                content_height: lv.content_height.clone(),
600                ctx_sub: sub.clone(),
601            };
602            let listview_width = read_logical_length(&sub, &lv.listview_width);
603            let _ = read_logical_length(&sub, &lv.listview_height);
604            Pin::as_ref(r).track_changes_listview_callback(&props, listview_width);
605        }
606        repeater.visit_maybe_instance(instance, order, visitor)
607    }
608
609    /// The z-sorted children of the node at `index`, or `None` if they aren't z-ordered.
610    /// Repeated children are expanded to one entry per instance.
611    pub fn compute_z_sorted_children(
612        self: Pin<&Self>,
613        index: isize,
614    ) -> Option<Vec<i_slint_core::item_tree::ZSortedChild>> {
615        use i_slint_core::item_tree::ZSortedChild;
616        if index < 0 {
617            return None;
618        }
619        let sources = self.z_sort_table.get(index as usize)?.as_ref()?;
620        let ItemTreeNode::Item { children_index, .. } = self.tree_nodes[index as usize] else {
621            return None;
622        };
623        let mut ctx = crate::eval::EvalContext::new(self.root_sub_component.clone());
624        let mut entries: Vec<ZSortedChild> = Vec::with_capacity(sources.len());
625        for (k, source) in sources.iter().enumerate() {
626            let child_offset = k as u32;
627            match source {
628                llr::ZSource::Expression(e) => {
629                    let z: f64 = crate::eval::eval_expression(&mut ctx, &e.borrow())
630                        .try_into()
631                        .unwrap_or(0.0);
632                    entries.push(ZSortedChild { z: z as f32, child_offset, instance: u32::MAX });
633                }
634                llr::ZSource::RepeaterInstances => {
635                    // The child is a `DynamicTree` node; its `dynamic_table` entry holds the repeater.
636                    if let Some((sub, rep_idx)) =
637                        self.get_ref().dynamic_at(children_index + child_offset)
638                    {
639                        sub.repeaters[rep_idx].for_each_instance_z(&mut |instance, z| {
640                            entries.push(ZSortedChild { z, child_offset, instance })
641                        });
642                    }
643                }
644            }
645        }
646        i_slint_core::item_tree::sort_z_entries(&mut entries);
647        Some(entries)
648    }
649
650    /// Build an instance for a public component.
651    ///
652    /// Properties are default-valued, then `bindings::install_bindings` wires
653    /// up `property_init`, `two_way_bindings` and `init_code`.
654    pub fn new(
655        compilation_unit: Rc<CompilationUnit>,
656        public_component_index: usize,
657    ) -> VRc<ItemTreeVTable, Instance> {
658        Self::new_with_window(compilation_unit, public_component_index, None, Default::default())
659    }
660
661    /// Build an instance for a public component and optionally reuse an
662    /// existing [`WindowAdapterRc`]. Live preview passes in the window from
663    /// the old instance so reloaded components keep the same window frame.
664    pub fn new_with_window(
665        compilation_unit: Rc<CompilationUnit>,
666        public_component_index: usize,
667        window_adapter: Option<i_slint_core::window::WindowAdapterRc>,
668        type_loaders: crate::component::TypeLoaders,
669    ) -> VRc<ItemTreeVTable, Instance> {
670        Self::new_with_options(
671            compilation_unit,
672            public_component_index,
673            window_adapter,
674            type_loaders,
675            None,
676        )
677    }
678
679    /// Build an instance embedded inside an existing item tree via a
680    /// `ComponentFactory`. Records the outer item tree handle and the
681    /// `ComponentContainer` slot index it substitutes into so that
682    /// `parent_node` can walk back into the host tree.
683    pub fn new_embedded(
684        compilation_unit: Rc<CompilationUnit>,
685        public_component_index: usize,
686        type_loaders: crate::component::TypeLoaders,
687        parent: vtable::VWeak<ItemTreeVTable>,
688        parent_item_tree_index: u32,
689    ) -> VRc<ItemTreeVTable, Instance> {
690        Self::new_with_options(
691            compilation_unit,
692            public_component_index,
693            None,
694            type_loaders,
695            Some((parent, parent_item_tree_index)),
696        )
697    }
698
699    fn new_with_options(
700        compilation_unit: Rc<CompilationUnit>,
701        public_component_index: usize,
702        window_adapter: Option<i_slint_core::window::WindowAdapterRc>,
703        type_loaders: crate::component::TypeLoaders,
704        embedded_in: Option<(vtable::VWeak<ItemTreeVTable>, u32)>,
705    ) -> VRc<ItemTreeVTable, Instance> {
706        let public = &compilation_unit.public_components[public_component_index];
707        let globals = Rc::new(GlobalStorage::new(&compilation_unit));
708        let item_tree = &public.item_tree;
709        let vrc = build_instance(
710            &compilation_unit,
711            item_tree,
712            Weak::new(),
713            globals,
714            Some(public_component_index),
715            type_loaders,
716        );
717        if let Some(adapter) = window_adapter {
718            let _ = vrc.window_adapter.set(adapter);
719        }
720        // Set the outer-tree handle before finalizing so bindings that
721        // read absolute coordinates during `install_bindings` /
722        // `init_code` can resolve `parent_node` through the host.
723        if let Some((parent, idx)) = embedded_in {
724            let _ = vrc.embedded_in.set((parent, idx));
725        }
726        finalize_instance(&vrc);
727        vrc
728    }
729
730    /// Build an instance for a repeated sub-tree, sharing `globals` with its
731    /// owning root instance.
732    /// `repeater_idx` lets `ModelDataAssignment` find the owning repeater
733    /// when an event in the repeated sub-tree wants to write back.
734    pub fn new_repeated(
735        compilation_unit: Rc<CompilationUnit>,
736        item_tree: &llr::ItemTree,
737        parent: Weak<SubComponentInstance>,
738        repeater_idx: RepeatedElementIdx,
739        globals: Rc<GlobalStorage>,
740    ) -> VRc<ItemTreeVTable, Instance> {
741        let vrc = build_instance(
742            &compilation_unit,
743            item_tree,
744            parent.clone(),
745            globals,
746            None,
747            Default::default(),
748        );
749        let _ = vrc.root_sub_component.repeated_in.set((parent, repeater_idx));
750        vrc
751    }
752
753    /// Build an instance for a popup sub-tree. The resulting `Instance` is
754    /// parented on the sub-component that owns the popup so that parent-
755    /// relative property references resolve through `parent.upgrade()`.
756    pub fn new_popup(
757        compilation_unit: Rc<CompilationUnit>,
758        item_tree: &llr::ItemTree,
759        parent: Weak<SubComponentInstance>,
760        globals: Rc<GlobalStorage>,
761    ) -> VRc<ItemTreeVTable, Instance> {
762        build_instance(&compilation_unit, item_tree, parent, globals, None, Default::default())
763    }
764}
765
766/// Allocate the `Instance` skeleton (sub-component tree, items, repeaters,
767/// tree nodes, globals) but do **not** install bindings yet.
768///
769/// Bindings install happens via [`finalize_instance`], which the caller
770/// invokes once the parent repeater (if any) has dropped its `RefCell`
771/// borrow. This avoids re-entrant repeater access when an `init` callback
772/// reads a layout property that walks back through the same repeater.
773fn build_instance(
774    compilation_unit: &Rc<CompilationUnit>,
775    item_tree: &llr::ItemTree,
776    parent: Weak<SubComponentInstance>,
777    globals: Rc<GlobalStorage>,
778    public_component_index: Option<usize>,
779    type_loaders: crate::component::TypeLoaders,
780) -> VRc<ItemTreeVTable, Instance> {
781    let parent_for_root = parent.clone();
782    let root_sub_component =
783        build_sub_component_instance(compilation_unit, item_tree.root, parent_for_root);
784    let (tree_nodes, dynamic_table, item_table, z_sort_table) = build_tree_nodes(&item_tree.tree);
785
786    let vrc = VRc::new(Instance {
787        root_sub_component,
788        tree_nodes: tree_nodes.into_boxed_slice(),
789        dynamic_table: dynamic_table.into_boxed_slice(),
790        item_table: item_table.into_boxed_slice(),
791        z_sort_table: z_sort_table.into_boxed_slice(),
792        globals,
793        self_weak: OnceCell::new(),
794        parent_instance: parent,
795        public_component_index,
796        window_adapter: OnceCell::new(),
797        window_adapter_error: OnceCell::new(),
798        window_attached: OnceCell::new(),
799        bindings_installed: OnceCell::new(),
800        init_code_run: OnceCell::new(),
801        embedded_in: OnceCell::new(),
802        type_loaders,
803    });
804    let weak = VRc::downgrade(&vrc);
805    let _ = vrc.self_weak.set(weak.clone());
806    // Repeated sub-trees and popups share their owner's storage; keep its root.
807    let _ = vrc.globals.root.set(weak.clone());
808    propagate_root(&vrc.root_sub_component, &weak);
809    vrc
810}
811
812/// Install global, sub-component and init bindings on a freshly built
813/// instance, then run `init_code`.
814///
815/// Idempotent: separate `OnceCell` flags guard the bindings install and
816/// the `init_code` step so each side can be called independently. The
817/// listview virtualization path uses
818/// [`install_bindings_for_repeated_row`] to install bindings before the
819/// first measurement and defers `init_code` to the core's
820/// `init_instances` callback (`<Instance as RepeatedItemTree>::init`).
821pub(crate) fn finalize_instance(vrc: &VRc<ItemTreeVTable, Instance>) {
822    install_bindings_for_repeated_row(vrc);
823    if vrc.init_code_run.get().is_some() {
824        return;
825    }
826    let _ = vrc.init_code_run.set(());
827    // For top-level instances, attach the window to the item tree *before*
828    // running init_code so `set_component` doesn't clear focus set by
829    // `forward-focus`. Embedded instances piggy-back on the host tree's
830    // adapter (see `window_adapter_or_default`) and skip this: the host
831    // has already run `set_component`, and running it again on the
832    // embedded root would reroute the host's window events into the sub-
833    // tree and clobber the ComponentContainer-driven size bindings.
834    if vrc.public_component_index.is_some() && vrc.embedded_in.get().is_none() {
835        vrc.attach_to_window();
836    }
837    // Call Item::init() on every native item and register the item tree
838    // with the window adapter. Registration matters: the rendering backend
839    // keeps per-component caches (text shaping, bounding rects) released
840    // only by the matching `unregister_item_tree` on Drop, and skipping
841    // the pair leaks entries until the renderer serves stale data for
842    // reused item addresses.
843    {
844        let dyn_rc = vtable::VRc::into_dyn(vrc.self_weak.get().unwrap().upgrade().unwrap());
845        let adapter = vrc.window_adapter_or_default();
846        i_slint_core::item_tree::register_item_tree(&dyn_rc, adapter);
847    }
848    crate::bindings::run_init_code_for_instance(vrc);
849}
850
851/// Install bindings, two-way links and timers on `vrc` without running
852/// `init_code`. Used by the listview row factory; safe to call from any
853/// other path that needs bindings in place but doesn't want to fire user
854/// init handlers yet.
855pub(crate) fn install_bindings_for_repeated_row(vrc: &VRc<ItemTreeVTable, Instance>) {
856    if vrc.bindings_installed.get().is_some() {
857        return;
858    }
859    let _ = vrc.bindings_installed.set(());
860    let is_root = vrc.parent_instance.upgrade().is_none();
861    if is_root {
862        crate::globals::install_global_bindings(&vrc.globals);
863    }
864    crate::bindings::install_bindings_only(vrc);
865}
866
867/// Back-fill the root weak reference on every sub-component under `sub`.
868fn propagate_root(sub: &Pin<Rc<SubComponentInstance>>, weak: &VWeak<ItemTreeVTable, Instance>) {
869    let _ = sub.root.set(weak.clone());
870    for nested in &sub.sub_components {
871        propagate_root(nested, weak);
872    }
873}
874
875/// Recursively allocate a [`SubComponentInstance`].
876fn build_sub_component_instance(
877    cu: &Rc<CompilationUnit>,
878    sub_idx: SubComponentIdx,
879    parent: Weak<SubComponentInstance>,
880) -> Pin<Rc<SubComponentInstance>> {
881    let sc = &cu.sub_components[sub_idx];
882    let registry = ItemRegistry::global();
883
884    let properties = sc
885        .properties
886        .iter()
887        .map(|p| Rc::pin(Property::new(crate::eval::default_value_for_type(&p.ty))))
888        .collect();
889    let callbacks = sc.callbacks.iter().map(|_| Rc::pin(Callback::default())).collect();
890    let callback_trackers =
891        sc.callbacks.iter().map(|c| c.needs_tracker.then(|| Rc::pin(Property::new(())))).collect();
892    let items =
893        sc.items
894            .iter()
895            .map(|item| {
896                registry.factory(&item.ty.class_name).unwrap_or_else(|| {
897                    panic!("native item `{}` is not registered", item.ty.class_name)
898                })()
899            })
900            .collect();
901    let repeaters = sc
902        .repeated
903        .iter()
904        .map(|rep| {
905            if rep.data_prop.is_none() {
906                RepeaterOrConditional::Conditional(Box::pin(Conditional::default()))
907            } else {
908                RepeaterOrConditional::Repeater(Box::pin(Repeater::default()))
909            }
910        })
911        .collect();
912
913    // `Rc::new_cyclic` gives nested sub-components a `Weak` to their parent.
914    // `SubComponentInstance` is `Unpin` (every pinned field lives behind its own
915    // `Pin<Rc<_>>`), so `Pin::new` on the resulting `Rc` needs no unsafe.
916    let rc = Rc::new_cyclic(|weak_self: &Weak<SubComponentInstance>| {
917        let sub_components = sc
918            .sub_components
919            .iter()
920            .map(|nested| build_sub_component_instance(cu, nested.ty, weak_self.clone()))
921            .collect();
922        SubComponentInstance {
923            compilation_unit: cu.clone(),
924            sub_component_idx: sub_idx,
925            properties,
926            callbacks,
927            callback_trackers,
928            items,
929            sub_components,
930            repeaters,
931            parent,
932            root: OnceCell::new(),
933            change_trackers: std::iter::repeat_with(ChangeTracker::default)
934                .take(2 * sc.timers.len() + sc.change_callbacks.len())
935                .collect(),
936            timers: std::iter::repeat_with(Default::default).take(sc.timers.len()).collect(),
937            popup_ids: vec![std::cell::Cell::new(None); sc.popup_windows.len()],
938            repeated_in: OnceCell::new(),
939            menubar: RefCell::new(None),
940        }
941    });
942    Pin::new(rc)
943}
944
945/// Read a `MemberReference` (rooted in `sub`) and convert the result to a
946/// `LogicalLength`. Used to seed the listview virtualization with the
947/// listview-width / listview-height values stored as `Value::Number`.
948fn read_logical_length(
949    sub: &Pin<Rc<SubComponentInstance>>,
950    mr: &llr::MemberReference,
951) -> i_slint_core::lengths::LogicalLength {
952    let mut ctx = crate::eval::EvalContext::new(sub.clone());
953    let v = crate::eval::load_property(&ctx, mr);
954    let _ = &mut ctx;
955    let n: f64 = v.try_into().unwrap_or(0.0);
956    i_slint_core::lengths::LogicalLength::new(n as f32)
957}
958
959/// Shim implementing [`i_slint_core::model::ListViewProperties`] over
960/// the interpreter's `Value`-typed content storage. The content
961/// references may be user-declared `Property<Value>` fields *or* native
962/// item properties (e.g. `Flickable::content-y`); routing through
963/// `load_property` / `store_property` handles both uniformly.
964struct ValueListViewProps {
965    content_y: llr::MemberReference,
966    /// `None` when the user set `content-width` explicitly, in which case
967    /// the ListView must not overwrite it (see #12264).
968    content_width: Option<llr::MemberReference>,
969    content_height: Option<llr::MemberReference>,
970    ctx_sub: Pin<Rc<SubComponentInstance>>,
971}
972
973impl i_slint_core::model::ListViewProperties for ValueListViewProps {
974    fn content_y_get(&self) -> i_slint_core::lengths::LogicalLength {
975        read_logical_length(&self.ctx_sub, &self.content_y)
976    }
977    fn content_y_get_internal(&self) -> i_slint_core::lengths::LogicalLength {
978        // The rtti route has no equivalent of `Property::get_internal`;
979        // reading normally only differs while a physics animation drives
980        // `content-y`, where it may re-evaluate the animated binding.
981        read_logical_length(&self.ctx_sub, &self.content_y)
982    }
983    fn content_y_set(&self, value: i_slint_core::lengths::LogicalLength) {
984        let ctx = crate::eval::EvalContext::new(self.ctx_sub.clone());
985        crate::eval::store_property(
986            &ctx,
987            &self.content_y,
988            crate::Value::Number(value.get() as f64),
989        );
990    }
991    fn content_y_has_binding(&self) -> bool {
992        // Unlike the generated code, the interpreter doesn't track whether
993        // the underlying property has an external binding; `false` lets
994        // `update_visible_instances` clamp the value when scrolling.
995        false
996    }
997    fn computes_content_height(&self) -> bool {
998        self.content_height.is_some()
999    }
1000    fn content_width_set(&self, value: i_slint_core::lengths::LogicalLength) {
1001        let Some(content_width) = &self.content_width else { return };
1002        let ctx = crate::eval::EvalContext::new(self.ctx_sub.clone());
1003        crate::eval::store_property(&ctx, content_width, crate::Value::Number(value.get() as f64));
1004    }
1005    fn content_height_set(&self, value: i_slint_core::lengths::LogicalLength) {
1006        let Some(content_height) = &self.content_height else { return };
1007        let ctx = crate::eval::EvalContext::new(self.ctx_sub.clone());
1008        crate::eval::store_property(&ctx, content_height, crate::Value::Number(value.get() as f64));
1009    }
1010    fn register_as_dependencies(&self) {
1011        // Reading through `load_property` registers the dependency with the
1012        // current tracking scope, which is all this hook needs.
1013        if let Some(content_width) = &self.content_width {
1014            let _ = read_logical_length(&self.ctx_sub, content_width);
1015        }
1016        if let Some(content_height) = &self.content_height {
1017            let _ = read_logical_length(&self.ctx_sub, content_height);
1018        }
1019        let _ = read_logical_length(&self.ctx_sub, &self.content_y);
1020    }
1021}
1022
1023type DynamicEntry = Option<(Box<[SubComponentInstanceIdx]>, RepeatedElementIdx)>;
1024type ItemEntry = Option<(Box<[SubComponentInstanceIdx]>, ItemInstanceIdx)>;
1025type ZSortEntry = Option<Vec<llr::ZSource>>;
1026
1027/// Flatten an LLR [`llr::TreeNode`] into the `ItemTreeNode` slice expected by
1028/// the `get_item_tree` vtable entry, plus three parallel tables: one mapping
1029/// flat indices to the dynamic repeaters they represent, one mapping static
1030/// flat indices to the sub-component path + items slot that owns them, and one
1031/// mapping flat indices whose children are dynamically z-ordered to the per-child
1032/// z sources.
1033///
1034/// Walks in the same order as [`llr::TreeNode::visit_in_array`], so flat
1035/// indices match what the rest of the runtime expects.
1036fn build_tree_nodes(
1037    root: &llr::TreeNode,
1038) -> (Vec<ItemTreeNode>, Vec<DynamicEntry>, Vec<ItemEntry>, Vec<ZSortEntry>) {
1039    use itertools::Either;
1040
1041    let mut out = Vec::new();
1042    let mut dyn_table: Vec<DynamicEntry> = Vec::new();
1043    let mut item_table: Vec<ItemEntry> = Vec::new();
1044    let mut z_sort_table: Vec<ZSortEntry> = Vec::new();
1045    root.visit_in_array(&mut |node, children_offset, parent_index| {
1046        let parent_index = parent_index as u32;
1047        let (entry, dyn_entry, item_entry) = match node.item_index {
1048            Either::Left(item_idx) => (
1049                ItemTreeNode::Item {
1050                    is_accessible: node.is_accessible,
1051                    children_count: node.children.len() as u32,
1052                    children_index: children_offset as u32,
1053                    parent_index,
1054                    // `item_array_index` is the flat tree index so
1055                    // `get_item_ref` can walk the item_table directly.
1056                    item_array_index: out.len() as u32,
1057                },
1058                None,
1059                Some((node.sub_component_path.clone().into_boxed_slice(), item_idx)),
1060            ),
1061            Either::Right(dynamic_index) => (
1062                // The `index` field on `DynamicTree` is opaque to the core:
1063                // whatever value we store here is echoed back to
1064                // `visit_dynamic_children` / `get_subtree_range` /
1065                // `get_subtree`. Use the flat tree index of this node so
1066                // those hooks can look up `dynamic_table` directly, rather
1067                // than the Rust-codegen convention of a global repeater
1068                // index that's unique across the sub-component tree.
1069                ItemTreeNode::DynamicTree { index: out.len() as u32, parent_index },
1070                Some((
1071                    node.sub_component_path.clone().into_boxed_slice(),
1072                    (dynamic_index as usize).into(),
1073                )),
1074                None,
1075            ),
1076        };
1077        out.push(entry);
1078        dyn_table.push(dyn_entry);
1079        item_table.push(item_entry);
1080        z_sort_table.push(node.z_sort_order_property.clone());
1081    });
1082    (out, dyn_table, item_table, z_sort_table)
1083}
1084
1085/// Lets [`Instance`] be used inside a `Repeater<C>`.
1086///
1087/// `update(idx, data)` writes the repeater's `index_prop` and `data_prop` on
1088/// the repeated instance's root sub-component.
1089impl i_slint_core::model::RepeatedItemTree for Instance {
1090    type Data = crate::Value;
1091
1092    fn update(&self, index: usize, data: Self::Data) {
1093        let sc_idx = self.root_sub_component.sub_component_idx;
1094        let cu = self.root_sub_component.compilation_unit.clone();
1095        let sc = &cu.sub_components[sc_idx];
1096        // `lower_sub_component` pushes `model_data` and `model_index` as the
1097        // first two properties of a repeated component's root sub-component.
1098        // Walk the full property list so user-declared `index` / `model-data`
1099        // shadows don't accidentally collide with slot 0/1.
1100        for (idx, prop) in sc.properties.iter_enumerated() {
1101            let target = &self.root_sub_component.properties[idx];
1102            match prop.name.as_str() {
1103                "model_data" => Pin::as_ref(target).set(data.clone()),
1104                "model_index" => Pin::as_ref(target).set(crate::Value::Number(index as f64)),
1105                _ => {}
1106            }
1107        }
1108    }
1109
1110    fn init(&self) {
1111        // Bindings and init code are installed here rather than in
1112        // `Instance::new_repeated`: by the time `init` runs,
1113        // `Repeater::ensure_updated` has released its `RefCell` borrow, so
1114        // a binding evaluated here can walk back through the same repeater
1115        // (e.g. an `init` callback that reads a layout property).
1116        if let Some(weak) = self.self_weak.get()
1117            && let Some(vrc) = weak.upgrade()
1118        {
1119            finalize_instance(&vrc);
1120        }
1121    }
1122
1123    fn z_order(self: Pin<&Self>) -> Option<f32> {
1124        // The z reference resolves in the repeated element's own context, so evaluate
1125        // it against this instance.
1126        let this = self.get_ref();
1127        let (parent_weak, rep_idx) = this.root_sub_component.repeated_in.get()?;
1128        let parent_sub = parent_weak.upgrade()?;
1129        let parent_sc = &parent_sub.compilation_unit.sub_components[parent_sub.sub_component_idx];
1130        let z_ref = parent_sc.repeated[*rep_idx].dynamic_z.as_ref()?;
1131        let ctx = crate::eval::EvalContext::new(this.root_sub_component.clone());
1132        let z: f64 = crate::eval::load_property(&ctx, z_ref).try_into().unwrap_or(0.0);
1133        Some(z as f32)
1134    }
1135
1136    fn listview_layout(
1137        self: Pin<&Self>,
1138        offset_y: &mut i_slint_core::lengths::LogicalLength,
1139    ) -> i_slint_core::lengths::LogicalLength {
1140        use i_slint_core::item_tree::ItemTree as _;
1141        use i_slint_core::lengths::LogicalLength;
1142        // Write `prop_y` on the repeated row's root sub-component, advance
1143        // `offset_y` by `prop_height`, and return the row's preferred
1144        // horizontal layout info width as the new content width estimate.
1145        let this = self.get_ref();
1146        let Some((parent_weak, rep_idx)) = this.root_sub_component.repeated_in.get() else {
1147            return LogicalLength::default();
1148        };
1149        let Some(parent_sub) = parent_weak.upgrade() else { return LogicalLength::default() };
1150        let parent_sub = Pin::new(parent_sub);
1151        let parent_cu = parent_sub.compilation_unit.clone();
1152        let parent_sc = &parent_cu.sub_components[parent_sub.sub_component_idx];
1153        let Some(lv) = parent_sc.repeated[*rep_idx].listview.as_ref() else {
1154            return LogicalLength::default();
1155        };
1156
1157        // `prop_y` and `prop_height` are member references in the repeated
1158        // sub-component's own context, so evaluate them against
1159        // `this.root_sub_component`.
1160        let row_sub = this.root_sub_component.clone();
1161        let ctx = crate::eval::EvalContext::new(row_sub.clone());
1162        crate::eval::store_property(&ctx, &lv.prop_y, crate::Value::Number(offset_y.get() as f64));
1163        let height_v = crate::eval::load_property(&ctx, &lv.prop_height);
1164        let height: f64 = height_v.try_into().unwrap_or(0.0);
1165        *offset_y += LogicalLength::new(height as f32);
1166        let info = self.layout_info(i_slint_core::items::Orientation::Horizontal);
1167        LogicalLength::new(info.min)
1168    }
1169
1170    fn layout_item_info(
1171        self: Pin<&Self>,
1172        orientation: i_slint_core::items::Orientation,
1173        child_index: Option<usize>,
1174    ) -> i_slint_core::layout::LayoutItemInfo {
1175        // Evaluate the repeated component's `layout_info_h` / `layout_info_v`
1176        // and wrap the result in a LayoutItemInfo.
1177        //
1178        // When the sub-component is a repeated Row with `row_child_templates`,
1179        // each `child_index` points at one concrete child position. Walk the
1180        // templates in declaration order and return per-child layout info —
1181        // static children read `grid_layout_children[idx]`, repeated children
1182        // forward to the inner repeater instance's own `layout_info`.
1183        let this = self.get_ref();
1184        let cu = this.root_sub_component.compilation_unit.clone();
1185        let sc_idx = this.root_sub_component.sub_component_idx;
1186        let sc = &cu.sub_components[sc_idx];
1187
1188        if let (Some(index), true, Some(templates)) =
1189            (child_index, sc.is_repeated_row, sc.row_child_templates.as_ref())
1190        {
1191            return row_child_layout_item_info(this, sc, templates, orientation, index);
1192        }
1193
1194        let expr = match orientation {
1195            i_slint_core::items::Orientation::Horizontal => sc.layout_info_h.borrow(),
1196            i_slint_core::items::Orientation::Vertical => sc.layout_info_v.borrow(),
1197        };
1198        let mut ctx = crate::eval::EvalContext::new(this.root_sub_component.clone());
1199        let constraint =
1200            crate::eval::eval_expression(&mut ctx, &expr).try_into().unwrap_or_default();
1201        // The cell's `cross-axis-self-alignment` in a box layout, returned for
1202        // the cross axis only, so the main-axis cache stays independent of it.
1203        let cross_axis_self_alignment = match &sc.cross_axis_self_alignment_for_repeated {
1204            Some((cross_o, align_expr))
1205                if crate::eval::llr_to_core_orientation(*cross_o) == orientation =>
1206            {
1207                crate::eval::eval_expression(&mut ctx, &align_expr.borrow())
1208                    .try_into()
1209                    .unwrap_or_default()
1210            }
1211            _ => Default::default(),
1212        };
1213        i_slint_core::layout::LayoutItemInfo { constraint, cross_axis_self_alignment }
1214    }
1215
1216    fn flexbox_layout_item_info(
1217        self: Pin<&Self>,
1218        orientation: i_slint_core::items::Orientation,
1219        child_index: Option<usize>,
1220    ) -> i_slint_core::layout::FlexboxLayoutItemInfo {
1221        // For flexbox, the SubComponent stores `flexbox_layout_item_info_for_repeated`
1222        // - an expression that evaluates to a `FlexboxLayoutItemInfo` struct.
1223        // Fall back to wrapping `layout_item_info` if it's not set.
1224        let cu = self.root_sub_component.compilation_unit.clone();
1225        let sc_idx = self.root_sub_component.sub_component_idx;
1226        let sc = &cu.sub_components[sc_idx];
1227        if let Some(expr) = &sc.flexbox_layout_item_info_for_repeated {
1228            let expr = expr.borrow();
1229            let mut ctx = crate::eval::EvalContext::new(self.root_sub_component.clone());
1230            let value = crate::eval::eval_expression(&mut ctx, &expr);
1231            let mut info = value_to_flexbox_layout_item_info(value, orientation, self);
1232            // Break the height-for-width recursion for a repeated instance in
1233            // a column FlexboxLayout: its vertical info must not read
1234            // self.width (set by the parent flex cache it is feeding). Use the
1235            // constrained vertical info (computed at the instance's own
1236            // preferred width) instead.
1237            if matches!(orientation, i_slint_core::items::Orientation::Vertical)
1238                && child_index.is_none()
1239                && let Some(v_expr) = &sc.layout_info_v_constrained_for_repeated
1240            {
1241                let mut ctx = crate::eval::EvalContext::new(self.root_sub_component.clone());
1242                info.constraint = crate::eval::eval_expression(&mut ctx, &v_expr.borrow())
1243                    .try_into()
1244                    .unwrap_or_default();
1245                return info;
1246            }
1247            // Mirror for the other axis: a width-for-height instance (e.g. a
1248            // wrapping column FlexboxLayout) must not read self.height. Use the
1249            // constrained horizontal info (computed at an unbounded height).
1250            if matches!(orientation, i_slint_core::items::Orientation::Horizontal)
1251                && child_index.is_none()
1252                && let Some(h_expr) = &sc.layout_info_h_constrained_for_repeated
1253            {
1254                let mut ctx = crate::eval::EvalContext::new(self.root_sub_component.clone());
1255                info.constraint = crate::eval::eval_expression(&mut ctx, &h_expr.borrow())
1256                    .try_into()
1257                    .unwrap_or_default();
1258                return info;
1259            }
1260            // The expression leaves the constraint unset; fill it with the
1261            // layout item's real constraint.
1262            info.constraint = self.layout_item_info(orientation, child_index).constraint;
1263            return info;
1264        }
1265        let info = self.layout_item_info(orientation, None);
1266        info.into()
1267    }
1268}
1269
1270impl Instance {
1271    /// Vertical flexbox info for a repeated instance measured at the container
1272    /// cross width instead of its own preferred width, so a height-for-width
1273    /// cell wraps to the same height as an equivalent static cell.
1274    pub fn flexbox_layout_item_info_at_cross_width(
1275        self: Pin<&Self>,
1276        flex_cross_width: f32,
1277    ) -> i_slint_core::layout::FlexboxLayoutItemInfo {
1278        use i_slint_core::items::Orientation;
1279        use i_slint_core::model::RepeatedItemTree;
1280        let mut info =
1281            RepeatedItemTree::flexbox_layout_item_info(self, Orientation::Vertical, None);
1282        let cu = self.root_sub_component.compilation_unit.clone();
1283        let sc = &cu.sub_components[self.root_sub_component.sub_component_idx];
1284        if let Some(v_expr) = &sc.layout_info_v_at_cross_width_for_repeated {
1285            let mut ctx = crate::eval::EvalContext::new(self.root_sub_component.clone());
1286            ctx.locals.insert(
1287                i_slint_compiler::llr::lower_layout_expression::FLEX_CROSS_WIDTH_LOCAL.into(),
1288                crate::Value::Number(flex_cross_width as f64),
1289            );
1290            info.constraint = crate::eval::eval_expression(&mut ctx, &v_expr.borrow())
1291                .try_into()
1292                .unwrap_or_default();
1293        }
1294        info
1295    }
1296
1297    /// Horizontal flexbox info for a repeated instance measured at the assigned
1298    /// cross height, so a width-for-height cell resolves to the same width as
1299    /// an equivalent static cell.
1300    pub fn flexbox_layout_item_info_at_cross_height(
1301        self: Pin<&Self>,
1302        flex_cross_height: f32,
1303    ) -> i_slint_core::layout::FlexboxLayoutItemInfo {
1304        use i_slint_core::items::Orientation;
1305        use i_slint_core::model::RepeatedItemTree;
1306        let mut info =
1307            RepeatedItemTree::flexbox_layout_item_info(self, Orientation::Horizontal, None);
1308        let cu = self.root_sub_component.compilation_unit.clone();
1309        let sc = &cu.sub_components[self.root_sub_component.sub_component_idx];
1310        if let Some(h_expr) = &sc.layout_info_h_at_cross_height_for_repeated {
1311            let mut ctx = crate::eval::EvalContext::new(self.root_sub_component.clone());
1312            ctx.locals.insert(
1313                i_slint_compiler::llr::lower_layout_expression::FLEX_CROSS_HEIGHT_LOCAL.into(),
1314                crate::Value::Number(flex_cross_height as f64),
1315            );
1316            info.constraint = crate::eval::eval_expression(&mut ctx, &h_expr.borrow())
1317                .try_into()
1318                .unwrap_or_default();
1319        }
1320        info
1321    }
1322}
1323
1324/// Walk the row_child_templates in declaration order, counting cells, until
1325/// the target `index` is reached. Static cells read from `grid_layout_children`;
1326/// a repeated cell forwards to the inner repeater instance's `layout_info`.
1327fn row_child_layout_item_info(
1328    this: &Instance,
1329    sc: &i_slint_compiler::llr::SubComponent,
1330    templates: &[i_slint_compiler::llr::RowChildTemplateInfo],
1331    orientation: i_slint_core::items::Orientation,
1332    mut index: usize,
1333) -> i_slint_core::layout::LayoutItemInfo {
1334    use i_slint_compiler::llr::RowChildTemplateInfo;
1335    use i_slint_core::model::RepeatedItemTree;
1336    for entry in templates {
1337        match entry {
1338            RowChildTemplateInfo::Static { child_index } => {
1339                if index == 0 {
1340                    let child = &sc.grid_layout_children[*child_index];
1341                    let expr = match orientation {
1342                        i_slint_core::items::Orientation::Horizontal => {
1343                            child.layout_info_h.borrow()
1344                        }
1345                        i_slint_core::items::Orientation::Vertical => child.layout_info_v.borrow(),
1346                    };
1347                    let mut ctx = crate::eval::EvalContext::new(this.root_sub_component.clone());
1348                    let constraint = crate::eval::eval_expression(&mut ctx, &expr)
1349                        .try_into()
1350                        .unwrap_or_default();
1351                    return i_slint_core::layout::LayoutItemInfo {
1352                        constraint,
1353                        ..Default::default()
1354                    };
1355                }
1356                index -= 1;
1357            }
1358            RowChildTemplateInfo::Repeated { repeater_index } => {
1359                let repeater = &this.root_sub_component.repeaters[*repeater_index];
1360                repeater.track_instance_changes();
1361                let count = repeater.range().len();
1362                if index < count {
1363                    if let Some(inner) = repeater.instance_at(index) {
1364                        return RepeatedItemTree::layout_item_info(
1365                            inner.as_pin_ref(),
1366                            orientation,
1367                            None,
1368                        );
1369                    }
1370                    return i_slint_core::layout::LayoutItemInfo::default();
1371                }
1372                index -= count;
1373            }
1374        }
1375    }
1376    i_slint_core::layout::LayoutItemInfo::default()
1377}
1378
1379fn value_to_flexbox_layout_item_info(
1380    v: crate::Value,
1381    orientation: i_slint_core::items::Orientation,
1382    instance: Pin<&Instance>,
1383) -> i_slint_core::layout::FlexboxLayoutItemInfo {
1384    use i_slint_core::model::RepeatedItemTree;
1385    let crate::Value::Struct(s) = v else {
1386        let info = RepeatedItemTree::layout_item_info(instance, orientation, None);
1387        return info.into();
1388    };
1389    crate::eval_layout::flexbox_item_info_from_struct(&s)
1390}