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