Skip to main content

i_slint_core/model/
repeater.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//! This module contains the [`Repeater`] and [`Conditional`] types that are
5//! used by generated code to instantiate items from a model using the `for`
6//! syntax, and [`RepeatedItemTree`] which is the trait implemented by the
7//! generated repeated components.
8//!
9//! The [`RepeaterInstanceOps`] trait abstracts over instance storage so the
10//! update algorithm can be shared between Rust and C++ (via FFI).
11
12use super::model_peer::{ModelChangeListener, ModelChangeListenerContainer};
13use super::{Model, ModelExt, ModelRc};
14use crate::item_tree::{ItemTreeVTable, TraversalOrder};
15use crate::layout::Orientation;
16use crate::lengths::{LogicalLength, RectLengths};
17use crate::{Coord, Property};
18use alloc::vec::Vec;
19use core::cell::RefCell;
20use core::pin::Pin;
21#[allow(unused)]
22use euclid::num::Floor;
23use pin_project::pin_project;
24
25type ItemTreeRc<C> = vtable::VRc<crate::item_tree::ItemTreeVTable, C>;
26
27/// ItemTree that can be instantiated by a repeater.
28pub trait RepeatedItemTree:
29    crate::item_tree::ItemTree + vtable::HasStaticVTable<ItemTreeVTable> + 'static
30{
31    /// The data corresponding to the model
32    type Data: Default + 'static;
33
34    /// Update this ItemTree at the given index and the given data
35    fn update(&self, index: usize, data: Self::Data);
36
37    /// Called once after the ItemTree has been instantiated and update()
38    /// was called once.
39    fn init(&self) {}
40
41    /// Layout this item in the listview
42    ///
43    /// offset_y is the `y` position where this item should be placed.
44    /// it should be updated to be to the y position of the next item.
45    ///
46    /// Returns the minimum item width which will be used to compute the listview's viewport width
47    fn listview_layout(self: Pin<&Self>, _offset_y: &mut LogicalLength) -> LogicalLength {
48        LogicalLength::default()
49    }
50
51    /// Returns what's needed to perform the layout if this ItemTree is in a layout
52    /// In case of repeated Rows, the index of a child item is set
53    fn layout_item_info(
54        self: Pin<&Self>,
55        _orientation: Orientation,
56        _child_index: Option<usize>,
57    ) -> crate::layout::LayoutItemInfo {
58        crate::layout::LayoutItemInfo::default()
59    }
60
61    /// Returns what's needed to perform a flexbox layout if this ItemTree is in a FlexboxLayout.
62    /// Includes flex-specific properties (flex-grow, flex-shrink, flex-basis, align-self, order).
63    fn flexbox_layout_item_info(
64        self: Pin<&Self>,
65        orientation: Orientation,
66        child_index: Option<usize>,
67    ) -> crate::layout::FlexboxLayoutItemInfo {
68        self.layout_item_info(orientation, child_index).into()
69    }
70
71    /// Fills in the grid layout input data for this ItemTree if it is in a grid layout.
72    /// This will be a single GridLayoutInputData if the repeated item is a single cell,
73    /// or multiple GridLayoutInputData if the repeated item is a full Row.
74    /// The slice must have the exact size required (known at compile time).
75    fn grid_layout_input_data(
76        self: Pin<&Self>,
77        _new_row: bool,
78        _result: &mut [crate::layout::GridLayoutInputData],
79    ) {
80        crate::debug_log!(
81            "Internal error in Slint: RepeatedItemTree::grid_layout_input_data() not implemented for {}",
82            core::any::type_name::<Self>()
83        );
84        // the actual implementation is in the code generated by generate_repeated_component()
85    }
86}
87
88#[derive(Clone, Copy, PartialEq, Debug)]
89enum RepeatedInstanceState {
90    /// The item is in a clean state
91    Clean,
92    /// The model data is stale and needs to be refreshed
93    Dirty,
94}
95struct RepeaterInner<C: RepeatedItemTree> {
96    instances: Vec<(RepeatedInstanceState, Option<ItemTreeRc<C>>)>,
97    /// ListView-specific layout state (offset, cached heights, scroll position).
98    layout_state: RepeaterLayoutState,
99}
100
101impl<C: RepeatedItemTree> Default for RepeaterInner<C> {
102    fn default() -> Self {
103        RepeaterInner { instances: Default::default(), layout_state: Default::default() }
104    }
105}
106
107/// Persistent layout state for a ListView repeater.
108#[derive(Default, Clone, Debug)]
109#[repr(C)]
110pub struct RepeaterLayoutState {
111    /// The model row index of the first instance in the collection.
112    pub offset: usize,
113    /// The average visible item height (cached between frames).
114    pub cached_item_height: Coord,
115    /// The viewport_y value from the previous layout pass.
116    /// It is used to detect if we are scrolling up or down
117    pub previous_viewport_y: Coord,
118    /// The y position of the item at `offset`.
119    pub anchor_y: Coord,
120}
121
122/// Abstraction over a repeater's instance collection so the same algorithm
123/// works for both native Rust repeaters and C++ repeaters via FFI.
124trait RepeaterInstanceOps {
125    /// Number of currently instantiated items.
126    fn len(&self) -> usize;
127
128    /// Replace the range `position..position+remove` with `add` new empty/dirty slots.
129    fn splice(&mut self, position: usize, remove: usize, add: usize);
130
131    /// If dirty, ensure the instance is created, initialized, and updated
132    /// for `row`. Returns `true` if freshly created.
133    fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool;
134
135    /// Height of the instance, or `None` if not yet created.
136    fn height(&self, instance_idx: usize) -> Option<Coord>;
137
138    /// Call `listview_layout` on the instance.
139    /// Advances `*y` to the next item position. Returns item width.
140    fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord;
141}
142
143/// Update all instances in the repeater, creating any that are missing.
144fn update_all_instances(ops: &mut impl RepeaterInstanceOps, offset: usize, count: usize) {
145    let cur = ops.len();
146    if count > cur {
147        ops.splice(cur, 0, count - cur);
148    } else if count < cur {
149        ops.splice(count, cur - count, 0);
150    }
151    for i in 0..count {
152        ops.ensure_updated(i, i + offset);
153    }
154}
155
156/// Update only the instances visible in the ListView viewport.
157///
158/// This is the core virtualization algorithm: it estimates which model rows
159/// are visible, instantiates/updates those, lays them out, and cleans up
160/// off-screen instances. Returns whether any instance was created.
161fn update_visible_instances(
162    ops: &mut impl RepeaterInstanceOps,
163    state: &mut RepeaterLayoutState,
164    row_count: usize,
165    viewport_width: Pin<&Property<LogicalLength>>,
166    viewport_height: Pin<&Property<LogicalLength>>,
167    viewport_y: Pin<&Property<LogicalLength>>,
168    listview_width: LogicalLength,
169    listview_height: LogicalLength,
170) -> bool {
171    let zero = LogicalLength::default();
172    let mut vp_width = listview_width.get();
173    let listview_height = listview_height.get();
174
175    if row_count == 0 {
176        ops.splice(0, ops.len(), 0);
177        viewport_height.set(zero);
178        viewport_y.set(zero);
179        viewport_width.set(listview_width);
180        return false;
181    }
182
183    let mut vp_y = viewport_y.get().get();
184    if !viewport_y.has_binding() {
185        vp_y = vp_y.min(0 as Coord);
186    }
187
188    let mut changed = false;
189
190    // Estimate element height from cached value or by measuring existing instances.
191    let element_height = if state.cached_item_height > 0 as Coord {
192        state.cached_item_height
193    } else {
194        let mut total_height: Coord = 0 as Coord;
195        let mut count = 0usize;
196        for i in 0..ops.len() {
197            if let Some(h) = ops.height(i) {
198                total_height += h;
199                count += 1;
200            }
201        }
202
203        if count > 0 {
204            total_height / count as Coord
205        } else {
206            // No items exist yet. Create one to measure.
207            state.offset = state.offset.min(row_count - 1);
208            ops.splice(0, ops.len(), 1);
209            changed |= ops.ensure_updated(0, state.offset);
210            ops.height(0).unwrap_or(0 as Coord)
211        }
212    };
213
214    if state.offset >= row_count {
215        state.offset = row_count - 1;
216    }
217
218    let one_and_a_half_screen = listview_height * 3 as Coord / 2 as Coord;
219    let first_item_y = state.anchor_y;
220    let last_item_bottom = first_item_y + element_height * ops.len() as Coord;
221
222    let (mut new_offset, mut new_offset_y) = if first_item_y > -vp_y + one_and_a_half_screen
223        || last_item_bottom + element_height < -vp_y
224    {
225        // Jumping more than 1.5 screens: random seek.
226        ops.splice(0, ops.len(), 0);
227        state.offset = ((-vp_y / element_height).floor() as usize).min(row_count - 1);
228        (state.offset, 0 as Coord)
229    } else if vp_y < state.previous_viewport_y {
230        // Scrolled down: find the new offset by walking existing instances.
231        let mut it_y = first_item_y + vp_y;
232        let mut new_off = state.offset;
233        for i in 0..ops.len() {
234            changed |= ops.ensure_updated(i, new_off);
235            let h = ops.height(i).unwrap_or(0 as Coord);
236            if it_y + h > 0 as Coord || new_off + 1 >= row_count {
237                break;
238            }
239            it_y += h;
240            new_off += 1;
241        }
242        (new_off, it_y)
243    } else {
244        // Scrolled up: will instantiate items before offset in the loop below.
245        (state.offset, first_item_y + vp_y)
246    };
247
248    let mut loop_count = 0;
249    loop {
250        // Fill gap before new_offset using already-instantiated items.
251        while new_offset > state.offset && new_offset_y > 0 as Coord {
252            new_offset -= 1;
253            new_offset_y -= ops.height(new_offset - state.offset).unwrap_or(0 as Coord);
254        }
255        // If there is still a gap, create new instances before the current ones.
256        let mut prepend_count = 0;
257        while new_offset > 0 && new_offset_y > 0 as Coord {
258            new_offset -= 1;
259            ops.splice(0, 0, 1);
260            changed |= ops.ensure_updated(0, new_offset);
261            new_offset_y -= ops.height(0).unwrap_or(0 as Coord);
262            prepend_count += 1;
263        }
264        if prepend_count > 0 {
265            state.offset = new_offset;
266        }
267        debug_assert!(new_offset >= state.offset && new_offset <= state.offset + ops.len());
268
269        // Layout items until we fill the view, starting with already-instantiated ones.
270        let mut y = new_offset_y;
271        let mut idx = new_offset;
272        let instances_begin = new_offset - state.offset;
273        for i in instances_begin..ops.len() {
274            if idx >= row_count {
275                break;
276            }
277            changed |= ops.ensure_updated(i, idx);
278            vp_width = vp_width.max(ops.listview_layout(i, &mut y));
279            idx += 1;
280            if y >= listview_height {
281                break;
282            }
283        }
284
285        // Create more items until there is no more room.
286        while y < listview_height && idx < row_count {
287            let i = ops.len();
288            ops.splice(i, 0, 1);
289            changed |= ops.ensure_updated(i, idx);
290            vp_width = vp_width.max(ops.listview_layout(i, &mut y));
291            idx += 1;
292        }
293
294        if y < listview_height && vp_y < 0 as Coord && loop_count < 3 {
295            debug_assert!(idx >= row_count);
296            // Reached end of model with room to spare. Scroll up.
297            vp_y += listview_height - y;
298            loop_count += 1;
299            continue;
300        }
301
302        // Clean up instances that are not shown.
303        if new_offset != state.offset {
304            let remove_count = new_offset - state.offset;
305            ops.splice(0, remove_count, 0);
306            state.offset = new_offset;
307        }
308        let keep = idx - new_offset;
309        if ops.len() > keep {
310            ops.splice(keep, ops.len() - keep, 0);
311        }
312
313        if ops.len() == 0 {
314            break;
315        }
316
317        // Recompute coordinates for the scrollbar.
318        state.cached_item_height = (y - new_offset_y) / ops.len() as Coord;
319        state.anchor_y = state.cached_item_height * state.offset as Coord;
320        viewport_height.set(LogicalLength::new(state.cached_item_height * row_count as Coord));
321        viewport_width.set(LogicalLength::new(vp_width));
322        let new_viewport_y = -state.anchor_y + new_offset_y;
323        // Important: Use get_internal here, the viewport_y may have a binding on it (especially
324        // a physical animation).
325        // We must not yet trigger a re-evaluation of that binding, as we have already updated the
326        // viewport_width and viewport_height, but the viewport_y is not yet consistent.
327        // So the physics animations limit value may be inconsistent.
328        if new_viewport_y != viewport_y.get_internal().get() {
329            // If a physics animation is ongoing (e.g. due to a flick), we should not interrupt it.
330            // The physics animation implements intercept_set, and is therefore not interrupted by
331            // a call to set() - so it's okay to just use a normal set here.
332            viewport_y.set(LogicalLength::new(new_viewport_y));
333        }
334        state.previous_viewport_y = new_viewport_y;
335
336        break;
337    }
338
339    changed
340}
341
342/// Adapter implementing [`RepeaterInstanceOps`] for the native Rust repeater.
343struct RustRepeaterOps<'a, C: RepeatedItemTree> {
344    inner: &'a RefCell<RepeaterInner<C>>,
345    init: &'a dyn Fn() -> ItemTreeRc<C>,
346    model: &'a ModelRc<C::Data>,
347}
348
349impl<C: RepeatedItemTree> RepeaterInstanceOps for RustRepeaterOps<'_, C> {
350    fn len(&self) -> usize {
351        self.inner.borrow().instances.len()
352    }
353
354    fn splice(&mut self, position: usize, remove: usize, add: usize) {
355        self.inner.borrow_mut().instances.splice(
356            position..position + remove,
357            core::iter::repeat_with(|| (RepeatedInstanceState::Dirty, None)).take(add),
358        );
359    }
360
361    fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool {
362        let (created, instance) = {
363            let mut inner = self.inner.borrow_mut();
364            let c = &mut inner.instances[instance_idx];
365            if c.0 != RepeatedInstanceState::Dirty {
366                return false;
367            }
368            let created = c.1.is_none();
369            if created {
370                c.1 = Some((self.init)());
371            }
372            c.1.as_ref().unwrap().update(row, self.model.row_data(row).unwrap_or_default());
373            c.0 = RepeatedInstanceState::Clean;
374            (created, c.1.as_ref().unwrap().clone())
375        };
376        if created {
377            crate::properties::evaluate_no_tracking(|| instance.init());
378        }
379        crate::item_tree::ensure_item_tree_instantiated(&vtable::VRc::into_dyn(instance));
380        created
381    }
382
383    fn height(&self, instance_idx: usize) -> Option<Coord> {
384        self.inner.borrow().instances[instance_idx]
385            .1
386            .as_ref()
387            .map(|x| x.as_pin_ref().item_geometry(0).height_length().get())
388    }
389
390    fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord {
391        let inner = self.inner.borrow();
392        let mut y_len = LogicalLength::new(*y);
393        let w = inner.instances[instance_idx]
394            .1
395            .as_ref()
396            .unwrap()
397            .as_pin_ref()
398            .listview_layout(&mut y_len);
399        *y = y_len.get();
400        w.get()
401    }
402}
403
404/// This struct is put in a component when using the `for` syntax
405/// It helps instantiating the ItemTree `T`
406#[pin_project]
407pub struct RepeaterTracker<T: RepeatedItemTree> {
408    inner: RefCell<RepeaterInner<T>>,
409    #[pin]
410    model: Property<ModelRc<T::Data>>,
411    #[pin]
412    /// Set to true when the model becomes dirty.
413    is_dirty: Property<bool>,
414    #[pin]
415    /// Marked dirty by `ensure_updated` when instances are added or
416    /// removed.  Layout and visit code register this as a dependency so
417    /// they re-evaluate only after the update pass materializes the
418    /// change, not when the model first becomes dirty.
419    instance_generation: Property<()>,
420    /// Only used for the list view to track if the scrollbar has changed and item needs to be laid out again.
421    #[pin]
422    listview_geometry_tracker: crate::properties::PropertyTracker,
423}
424
425impl<T: RepeatedItemTree> ModelChangeListener for RepeaterTracker<T> {
426    /// Notify the peers that a specific row was changed
427    fn row_changed(self: Pin<&Self>, row: usize) {
428        let mut inner = self.inner.borrow_mut();
429        let inner = &mut *inner;
430        if let Some(c) = inner.instances.get_mut(row.wrapping_sub(inner.layout_state.offset)) {
431            if !self.model.is_dirty() {
432                if let Some(comp) = c.1.as_ref() {
433                    let model = self.project_ref().model.get_untracked();
434                    comp.update(row, model.row_data(row).unwrap_or_default());
435                    c.0 = RepeatedInstanceState::Clean;
436                }
437            } else {
438                c.0 = RepeatedInstanceState::Dirty;
439            }
440        }
441    }
442    /// Notify the peers that rows were added
443    fn row_added(self: Pin<&Self>, mut index: usize, mut count: usize) {
444        let mut inner = self.inner.borrow_mut();
445        if index < inner.layout_state.offset {
446            if index + count <= inner.layout_state.offset {
447                // Entirely before the visible range: shift the offset.
448                inner.layout_state.offset += count;
449                self.is_dirty.set(true);
450                for c in inner.instances.iter_mut() {
451                    c.0 = RepeatedInstanceState::Dirty;
452                }
453                return;
454            }
455            count -= inner.layout_state.offset - index;
456            index = 0;
457        } else {
458            index -= inner.layout_state.offset;
459        }
460        if count == 0 || index > inner.instances.len() {
461            return;
462        }
463        self.is_dirty.set(true);
464        inner.instances.splice(
465            index..index,
466            core::iter::repeat_n((RepeatedInstanceState::Dirty, None), count),
467        );
468        for c in inner.instances[index + count..].iter_mut() {
469            // Because all the indexes are dirty
470            c.0 = RepeatedInstanceState::Dirty;
471        }
472    }
473    /// Notify the peers that rows were removed
474    fn row_removed(self: Pin<&Self>, mut index: usize, mut count: usize) {
475        let mut inner = self.inner.borrow_mut();
476        if index < inner.layout_state.offset {
477            if index + count <= inner.layout_state.offset {
478                // Entirely before the visible range: shift the offset.
479                inner.layout_state.offset -= count;
480                self.is_dirty.set(true);
481                for c in inner.instances.iter_mut() {
482                    c.0 = RepeatedInstanceState::Dirty;
483                }
484                return;
485            }
486            count -= inner.layout_state.offset - index;
487            inner.layout_state.offset = index;
488            index = 0;
489        } else {
490            index -= inner.layout_state.offset;
491        }
492        if count == 0 || index >= inner.instances.len() {
493            return;
494        }
495        if (index + count) > inner.instances.len() {
496            count = inner.instances.len() - index;
497        }
498        self.is_dirty.set(true);
499        inner.instances.drain(index..(index + count));
500        for c in inner.instances[index..].iter_mut() {
501            // Because all the indexes are dirty
502            c.0 = RepeatedInstanceState::Dirty;
503        }
504    }
505
506    fn reset(self: Pin<&Self>) {
507        self.is_dirty.set(true);
508        self.inner.borrow_mut().instances.clear();
509    }
510}
511
512impl<C: RepeatedItemTree> Default for RepeaterTracker<C> {
513    fn default() -> Self {
514        Self {
515            inner: Default::default(),
516            model: Property::new_named(ModelRc::default(), "i_slint_core::Repeater::model"),
517            is_dirty: Property::new_named(false, "i_slint_core::Repeater::is_dirty"),
518            instance_generation: Property::new_named(
519                (),
520                "i_slint_core::Repeater::instance_generation",
521            ),
522            listview_geometry_tracker: Default::default(),
523        }
524    }
525}
526
527#[pin_project]
528pub struct Repeater<C: RepeatedItemTree>(#[pin] ModelChangeListenerContainer<RepeaterTracker<C>>);
529
530impl<C: RepeatedItemTree> Default for Repeater<C> {
531    fn default() -> Self {
532        Self(Default::default())
533    }
534}
535
536impl<C: RepeatedItemTree + 'static> Repeater<C> {
537    fn data(self: Pin<&Self>) -> Pin<&RepeaterTracker<C>> {
538        self.project_ref().0.get()
539    }
540
541    /// Register the model and dirty flag as dependencies of the current
542    /// tracking scope (e.g. the redraw tracker) so it is notified when the
543    /// model or its data changes.
544    pub fn track_model_changes(self: Pin<&Self>) {
545        self.data().project_ref().model.register_as_dependency();
546        self.data().project_ref().is_dirty.register_as_dependency();
547    }
548
549    /// Register the instance generation as a dependency of the current
550    /// tracking scope. This is for layout and visit code that should
551    /// re-evaluate only after `ensure_updated` has materialized instance
552    /// changes, not when the model first becomes dirty.
553    pub fn track_instance_changes(self: Pin<&Self>) {
554        self.data().project_ref().instance_generation.register_as_dependency();
555    }
556
557    fn model(self: Pin<&Self>) -> ModelRc<C::Data> {
558        let model = self.data().project_ref().model;
559
560        if model.is_dirty() {
561            let old_model = model.get_internal();
562            let m = model.get();
563            if old_model != m {
564                *self.data().inner.borrow_mut() = RepeaterInner::default();
565                self.data().is_dirty.set(true);
566                let peer = self.project_ref().0.model_peer();
567                m.model_tracker().attach_peer(peer);
568            }
569            m
570        } else {
571            model.get()
572        }
573    }
574
575    /// Call this function to make sure that the model is updated.
576    /// The init function is the function to create a ItemTree.
577    /// Returns `true` if instances were actually created or removed.
578    /// Also recurses into child instances to ensure they are instantiated.
579    pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) -> bool {
580        let model = self.model();
581        let changed = if self.data().project_ref().is_dirty.get() {
582            let count = model.row_count();
583            let offset = self.0.inner.borrow().layout_state.offset;
584            let mut ops = RustRepeaterOps { inner: &self.0.inner, init: &init, model: &model };
585            self.data().is_dirty.set(false);
586            update_all_instances(&mut ops, offset, count);
587            self.data().instance_generation.mark_dirty();
588            true
589        } else {
590            false
591        };
592        self.ensure_children_instantiated() || changed
593    }
594
595    /// Recurse into child instances to ensure they are instantiated.
596    fn ensure_children_instantiated(&self) -> bool {
597        let mut changed = false;
598        for instance in self.instances_vec() {
599            changed |=
600                crate::item_tree::ensure_item_tree_instantiated(&vtable::VRc::into_dyn(instance));
601        }
602        changed
603    }
604
605    /// Register the ListView viewport properties as dependencies so that
606    /// scrolling triggers a redraw.  Model dependencies are registered by
607    /// [`Self::visit`], so this only covers the viewport geometry.
608    pub fn track_changes_listview(
609        self: Pin<&Self>,
610        viewport_width: Pin<&Property<LogicalLength>>,
611        viewport_height: Pin<&Property<LogicalLength>>,
612        viewport_y: Pin<&Property<LogicalLength>>,
613        listview_width: LogicalLength,
614        listview_height: Pin<&Property<LogicalLength>>,
615    ) {
616        viewport_width.register_as_dependency();
617        viewport_height.register_as_dependency();
618        viewport_y.register_as_dependency();
619        // listview_width is passed as a value, not a property, so it cannot
620        // be registered as a dependency. Kept in the signature for symmetry
621        // with ensure_updated_listview.
622        let _ = listview_width;
623        listview_height.register_as_dependency();
624    }
625
626    /// Same as `Self::ensure_updated` but for a ListView.
627    /// Returns `true` if any instances were created or any child changed.
628    pub fn ensure_updated_listview(
629        self: Pin<&Self>,
630        init: impl Fn() -> ItemTreeRc<C>,
631        viewport_width: Pin<&Property<LogicalLength>>,
632        viewport_height: Pin<&Property<LogicalLength>>,
633        viewport_y: Pin<&Property<LogicalLength>>,
634        listview_width: LogicalLength,
635        listview_height: Pin<&Property<LogicalLength>>,
636    ) -> bool {
637        self.data().project_ref().is_dirty.set(false);
638
639        let model = self.model();
640        let row_count = model.row_count();
641
642        let data = self.data();
643        let mut layout_state = data.inner.borrow().layout_state.clone();
644        let mut ops = RustRepeaterOps { inner: &data.inner, init: &init, model: &model };
645        let changed = update_visible_instances(
646            &mut ops,
647            &mut layout_state,
648            row_count,
649            viewport_width,
650            viewport_height,
651            viewport_y,
652            listview_width,
653            listview_height.get(),
654        );
655        data.inner.borrow_mut().layout_state = layout_state;
656
657        if changed {
658            self.data().instance_generation.mark_dirty();
659        }
660        self.ensure_children_instantiated() || changed
661    }
662
663    /// Sets the data directly in the model
664    pub fn model_set_row_data(self: Pin<&Self>, row: usize, data: C::Data) {
665        let model = self.model();
666        model.set_row_data(row, data);
667    }
668
669    /// Read a row from the model, registering a dependency on it when
670    /// called from a binding evaluation.
671    pub fn model_row_data(self: Pin<&Self>, row: usize) -> Option<C::Data> {
672        self.model().row_data_tracked(row)
673    }
674
675    /// Set the model binding
676    pub fn set_model_binding(&self, binding: impl Fn() -> ModelRc<C::Data> + 'static) {
677        self.0.model.set_binding(binding);
678    }
679
680    /// Call the visitor for the root of each instance.
681    /// Also registers model dependencies so the current tracking scope
682    /// (e.g. the redraw tracker) is notified when the model changes.
683    pub fn visit(
684        self: Pin<&Self>,
685        order: TraversalOrder,
686        mut visitor: crate::item_tree::ItemVisitorRefMut,
687    ) -> crate::item_tree::VisitChildrenResult {
688        self.track_model_changes();
689        // We can't keep self.inner borrowed because the event might modify the model
690        let count = self.0.inner.borrow().instances.len() as u32;
691        for i in 0..count {
692            let i = if order == TraversalOrder::BackToFront { i } else { count - i - 1 };
693            let c = self.0.inner.borrow().instances.get(i as usize).and_then(|c| c.1.clone());
694            if let Some(c) = c
695                && c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
696            {
697                return crate::item_tree::VisitChildrenResult::abort(i, 0);
698            }
699        }
700        crate::item_tree::VisitChildrenResult::CONTINUE
701    }
702
703    /// Return the amount of instances currently in the repeater
704    pub fn len(&self) -> usize {
705        self.0.inner.borrow().instances.len()
706    }
707
708    /// Return the range of indices used by this Repeater.
709    ///
710    /// Two values are necessary here since the Repeater can start to insert the data from its
711    /// model at an offset.
712    pub fn range(&self) -> core::ops::Range<usize> {
713        let inner = self.0.inner.borrow();
714        core::ops::Range {
715            start: inner.layout_state.offset,
716            end: inner.layout_state.offset + inner.instances.len(),
717        }
718    }
719
720    /// Return the instance for the given model index.
721    /// The index should be within [`Self::range()`]
722    pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
723        let inner = self.0.inner.borrow();
724        inner.instances.get(index.checked_sub(inner.layout_state.offset)?).and_then(|c| c.1.clone())
725    }
726
727    /// Return true if the Repeater as empty
728    pub fn is_empty(&self) -> bool {
729        self.len() == 0
730    }
731
732    /// Returns a vector containing all instances
733    pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
734        self.0.inner.borrow().instances.iter().flat_map(|x| x.1.clone()).collect()
735    }
736}
737
738#[pin_project]
739pub struct Conditional<C: RepeatedItemTree> {
740    #[pin]
741    model: Property<bool>,
742    #[pin]
743    instance_generation: Property<()>,
744    instance: RefCell<Option<ItemTreeRc<C>>>,
745}
746
747impl<C: RepeatedItemTree> Default for Conditional<C> {
748    fn default() -> Self {
749        Self {
750            model: Property::new_named(false, "i_slint_core::Conditional::model"),
751            instance_generation: Property::new_named(
752                (),
753                "i_slint_core::Conditional::instance_generation",
754            ),
755            instance: RefCell::new(None),
756        }
757    }
758}
759
760impl<C: RepeatedItemTree + 'static> Conditional<C> {
761    /// Register the condition as a dependency of the current tracking scope
762    /// (e.g. the redraw tracker) so it is notified when the condition changes.
763    pub fn track_model_changes(self: Pin<&Self>) {
764        self.project_ref().model.register_as_dependency();
765    }
766
767    /// Register the instance generation as a dependency of the current
768    /// tracking scope. Layout code uses this to re-evaluate only after
769    /// `ensure_updated` materializes instance changes.
770    pub fn track_instance_changes(self: Pin<&Self>) {
771        self.project_ref().instance_generation.register_as_dependency();
772    }
773
774    /// Call this function to make sure that the model is updated.
775    /// The init function is the function to create a ItemTree.
776    /// Returns `true` if the instance was created or removed, or any child changed.
777    pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) -> bool {
778        let model = self.project_ref().model.get();
779
780        let changed = if !model {
781            self.instance.take().is_some()
782        } else if self.instance.borrow().is_none() {
783            let i = init();
784            self.instance.replace(Some(i.clone()));
785            i.init();
786            true
787        } else {
788            false
789        };
790        if changed {
791            self.instance_generation.mark_dirty();
792        }
793        if let Some(instance) = self.instance.borrow().as_ref() {
794            crate::item_tree::ensure_item_tree_instantiated(&vtable::VRc::into_dyn(
795                instance.clone(),
796            )) || changed
797        } else {
798            changed
799        }
800    }
801
802    /// Set the model binding
803    pub fn set_model_binding(&self, binding: impl Fn() -> bool + 'static) {
804        self.model.set_binding(binding);
805    }
806
807    /// Call the visitor for the root of each instance.
808    /// Also registers model dependencies so the current tracking scope
809    /// (e.g. the redraw tracker) is notified when the condition changes.
810    pub fn visit(
811        self: Pin<&Self>,
812        order: TraversalOrder,
813        mut visitor: crate::item_tree::ItemVisitorRefMut,
814    ) -> crate::item_tree::VisitChildrenResult {
815        self.track_model_changes();
816        // We can't keep self.inner borrowed because the event might modify the model
817        let instance = self.instance.borrow().clone();
818        if let Some(c) = instance
819            && c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
820        {
821            return crate::item_tree::VisitChildrenResult::abort(0, 0);
822        }
823
824        crate::item_tree::VisitChildrenResult::CONTINUE
825    }
826
827    /// Return the amount of instances (1 if the conditional is active, 0 otherwise)
828    pub fn len(&self) -> usize {
829        self.instance.borrow().is_some() as usize
830    }
831
832    /// Return the range of indices used by this Conditional.
833    ///
834    /// Similar to Repeater::range, but the range is always [0, 1] if the Conditional is active.
835    pub fn range(&self) -> core::ops::Range<usize> {
836        0..self.len()
837    }
838
839    /// Return the instance for the given model index.
840    /// The index should be within [`Self::range()`]
841    pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
842        if index != 0 {
843            return None;
844        }
845        self.instance.borrow().clone()
846    }
847
848    /// Return true if the Repeater as empty
849    pub fn is_empty(&self) -> bool {
850        self.len() == 0
851    }
852
853    /// Returns a vector containing all instances
854    pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
855        self.instance.borrow().clone().into_iter().collect()
856    }
857}
858
859#[cfg(feature = "ffi")]
860mod ffi {
861    #![allow(unsafe_code)]
862
863    use super::*;
864
865    /// C++ callback table for [`RepeaterInstanceOps`], including the opaque
866    /// user_data pointer that is passed to each callback.
867    #[repr(C)]
868    pub struct RepeaterInstanceOpsVTable {
869        pub user_data: *mut core::ffi::c_void,
870        pub len: unsafe extern "C" fn(user_data: *mut core::ffi::c_void) -> usize,
871        pub splice: unsafe extern "C" fn(
872            user_data: *mut core::ffi::c_void,
873            position: usize,
874            remove: usize,
875            add: usize,
876        ),
877        pub ensure_updated: unsafe extern "C" fn(
878            user_data: *mut core::ffi::c_void,
879            instance_idx: usize,
880            row: usize,
881        ) -> bool,
882        /// Height of instance, or NaN if not yet created.
883        pub height:
884            unsafe extern "C" fn(user_data: *mut core::ffi::c_void, instance_idx: usize) -> Coord,
885        pub listview_layout: Option<
886            unsafe extern "C" fn(
887                user_data: *mut core::ffi::c_void,
888                instance_idx: usize,
889                y: &mut Coord,
890            ) -> Coord,
891        >,
892        pub init: unsafe extern "C" fn(user_data: *mut core::ffi::c_void, instance_idx: usize),
893    }
894
895    impl RepeaterInstanceOps for RepeaterInstanceOpsVTable {
896        fn len(&self) -> usize {
897            unsafe { (self.len)(self.user_data) }
898        }
899        fn splice(&mut self, position: usize, remove: usize, add: usize) {
900            unsafe { (self.splice)(self.user_data, position, remove, add) }
901        }
902        fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool {
903            let created = unsafe { (self.ensure_updated)(self.user_data, instance_idx, row) };
904            if created {
905                unsafe { (self.init)(self.user_data, instance_idx) };
906            }
907            created
908        }
909        fn height(&self, instance_idx: usize) -> Option<Coord> {
910            let h = unsafe { (self.height)(self.user_data, instance_idx) };
911            if h.is_nan() { None } else { Some(h) }
912        }
913        fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord {
914            self.listview_layout
915                .map_or(0 as Coord, |f| unsafe { f(self.user_data, instance_idx, y) })
916        }
917    }
918
919    #[unsafe(no_mangle)]
920    pub extern "C" fn slint_repeater_ensure_updated(
921        ops: &mut RepeaterInstanceOpsVTable,
922        offset: usize,
923        count: usize,
924    ) {
925        update_all_instances(ops, offset, count);
926    }
927
928    #[unsafe(no_mangle)]
929    pub extern "C" fn slint_repeater_ensure_updated_listview(
930        ops: &mut RepeaterInstanceOpsVTable,
931        state: &mut RepeaterLayoutState,
932        row_count: usize,
933        viewport_width: Pin<&Property<LogicalLength>>,
934        viewport_height: Pin<&Property<LogicalLength>>,
935        viewport_y: Pin<&Property<LogicalLength>>,
936        listview_width: LogicalLength,
937        listview_height: LogicalLength,
938    ) -> bool {
939        update_visible_instances(
940            ops,
941            state,
942            row_count,
943            viewport_width,
944            viewport_height,
945            viewport_y,
946            listview_width,
947            listview_height,
948        )
949    }
950}