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