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, 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 and updated for `row`.
132    /// Returns `true` if freshly created (needs init later).
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.
144/// Returns indices of newly created instances (that need `init()` called).
145fn update_all_instances(
146    ops: &mut impl RepeaterInstanceOps,
147    offset: usize,
148    count: usize,
149) -> Vec<usize> {
150    let cur = ops.len();
151    if count > cur {
152        ops.splice(cur, 0, count - cur);
153    } else if count < cur {
154        ops.splice(count, cur - count, 0);
155    }
156    let mut indices_to_init = Vec::new();
157    for i in 0..count {
158        if ops.ensure_updated(i, i + offset) {
159            indices_to_init.push(i);
160        }
161    }
162    indices_to_init
163}
164
165/// Update only the instances visible in the ListView viewport.
166///
167/// This is the core virtualization algorithm: it estimates which model rows
168/// are visible, instantiates/updates those, lays them out, and cleans up
169/// off-screen instances. Returns indices of newly created instances.
170fn update_visible_instances(
171    ops: &mut impl RepeaterInstanceOps,
172    state: &mut RepeaterLayoutState,
173    row_count: usize,
174    viewport_width: Pin<&Property<LogicalLength>>,
175    viewport_height: Pin<&Property<LogicalLength>>,
176    viewport_y: Pin<&Property<LogicalLength>>,
177    listview_width: LogicalLength,
178    listview_height: LogicalLength,
179) -> Vec<usize> {
180    let zero = LogicalLength::default();
181    let mut vp_width = listview_width.get();
182    let listview_height = listview_height.get();
183
184    if row_count == 0 {
185        ops.splice(0, ops.len(), 0);
186        viewport_height.set(zero);
187        viewport_y.set(zero);
188        viewport_width.set(listview_width);
189        return Vec::new();
190    }
191
192    let mut vp_y = viewport_y.get().get();
193    if !viewport_y.has_binding() {
194        vp_y = vp_y.min(0 as Coord);
195    }
196
197    let mut indices_to_init = Vec::new();
198
199    // Estimate element height from cached value or by measuring existing instances.
200    let element_height = if state.cached_item_height > 0 as Coord {
201        state.cached_item_height
202    } else {
203        let mut total_height: Coord = 0 as Coord;
204        let mut count = 0usize;
205        for i in 0..ops.len() {
206            if let Some(h) = ops.height(i) {
207                total_height += h;
208                count += 1;
209            }
210        }
211
212        if count > 0 {
213            total_height / count as Coord
214        } else {
215            // No items exist yet. Create one to measure.
216            state.offset = state.offset.min(row_count - 1);
217            ops.splice(0, ops.len(), 1);
218            if ops.ensure_updated(0, state.offset) {
219                indices_to_init.push(0);
220            }
221            ops.height(0).unwrap_or(0 as Coord)
222        }
223    };
224
225    if state.offset >= row_count {
226        state.offset = row_count - 1;
227    }
228
229    let one_and_a_half_screen = listview_height * 3 as Coord / 2 as Coord;
230    let first_item_y = state.anchor_y;
231    let last_item_bottom = first_item_y + element_height * ops.len() as Coord;
232
233    let (mut new_offset, mut new_offset_y) = if first_item_y > -vp_y + one_and_a_half_screen
234        || last_item_bottom + element_height < -vp_y
235    {
236        // Jumping more than 1.5 screens: random seek.
237        ops.splice(0, ops.len(), 0);
238        state.offset = ((-vp_y / element_height).floor() as usize).min(row_count - 1);
239        (state.offset, 0 as Coord)
240    } else if vp_y < state.previous_viewport_y {
241        // Scrolled down: find the new offset by walking existing instances.
242        let mut it_y = first_item_y + vp_y;
243        let mut new_off = state.offset;
244        for i in 0..ops.len() {
245            if ops.ensure_updated(i, new_off) {
246                indices_to_init.push(i);
247            }
248            let h = ops.height(i).unwrap_or(0 as Coord);
249            if it_y + h > 0 as Coord || new_off + 1 >= row_count {
250                break;
251            }
252            it_y += h;
253            new_off += 1;
254        }
255        (new_off, it_y)
256    } else {
257        // Scrolled up: will instantiate items before offset in the loop below.
258        (state.offset, first_item_y + vp_y)
259    };
260
261    let mut loop_count = 0;
262    loop {
263        // Fill gap before new_offset using already-instantiated items.
264        while new_offset > state.offset && new_offset_y > 0 as Coord {
265            new_offset -= 1;
266            new_offset_y -= ops.height(new_offset - state.offset).unwrap_or(0 as Coord);
267        }
268        // If there is still a gap, create new instances before the current ones.
269        let mut prepend_count = 0;
270        while new_offset > 0 && new_offset_y > 0 as Coord {
271            new_offset -= 1;
272            ops.splice(0, 0, 1);
273            ops.ensure_updated(0, new_offset);
274            new_offset_y -= ops.height(0).unwrap_or(0 as Coord);
275            prepend_count += 1;
276        }
277        if prepend_count > 0 {
278            for x in &mut indices_to_init {
279                *x += prepend_count;
280            }
281            indices_to_init.extend(0..prepend_count);
282            state.offset = new_offset;
283        }
284        debug_assert!(new_offset >= state.offset && new_offset <= state.offset + ops.len());
285
286        // Layout items until we fill the view, starting with already-instantiated ones.
287        let mut y = new_offset_y;
288        let mut idx = new_offset;
289        let instances_begin = new_offset - state.offset;
290        for i in instances_begin..ops.len() {
291            if idx >= row_count {
292                break;
293            }
294            if ops.ensure_updated(i, idx) {
295                indices_to_init.push(i);
296            }
297            vp_width = vp_width.max(ops.listview_layout(i, &mut y));
298            idx += 1;
299            if y >= listview_height {
300                break;
301            }
302        }
303
304        // Create more items until there is no more room.
305        while y < listview_height && idx < row_count {
306            let i = ops.len();
307            ops.splice(i, 0, 1);
308            ops.ensure_updated(i, idx);
309            indices_to_init.push(i);
310            vp_width = vp_width.max(ops.listview_layout(i, &mut y));
311            idx += 1;
312        }
313
314        if y < listview_height && vp_y < 0 as Coord && loop_count < 3 {
315            debug_assert!(idx >= row_count);
316            // Reached end of model with room to spare. Scroll up.
317            vp_y += listview_height - y;
318            loop_count += 1;
319            continue;
320        }
321
322        // Clean up instances that are not shown.
323        if new_offset != state.offset {
324            let remove_count = new_offset - state.offset;
325            ops.splice(0, remove_count, 0);
326            indices_to_init.retain_mut(|i| {
327                if *i < remove_count {
328                    false
329                } else {
330                    *i -= remove_count;
331                    true
332                }
333            });
334            state.offset = new_offset;
335        }
336        let keep = idx - new_offset;
337        if ops.len() > keep {
338            ops.splice(keep, ops.len() - keep, 0);
339            indices_to_init.retain(|x| *x < keep);
340        }
341
342        if ops.len() == 0 {
343            break;
344        }
345
346        // Recompute coordinates for the scrollbar.
347        state.cached_item_height = (y - new_offset_y) / ops.len() as Coord;
348        state.anchor_y = state.cached_item_height * state.offset as Coord;
349        viewport_height.set(LogicalLength::new(state.cached_item_height * row_count as Coord));
350        viewport_width.set(LogicalLength::new(vp_width));
351        // If an animation is ongoing we should not interrupt it
352        if !viewport_y.has_binding() {
353            let new_viewport_y = -state.anchor_y + new_offset_y;
354            if new_viewport_y != viewport_y.get().get() {
355                viewport_y.set(LogicalLength::new(new_viewport_y));
356            }
357            state.previous_viewport_y = new_viewport_y;
358        } else {
359            state.previous_viewport_y = viewport_y.get().0;
360        }
361        break;
362    }
363
364    indices_to_init
365}
366
367/// Adapter implementing [`RepeaterInstanceOps`] for the native Rust repeater.
368struct RustRepeaterOps<'a, C: RepeatedItemTree> {
369    instances: &'a mut Vec<(RepeatedInstanceState, Option<ItemTreeRc<C>>)>,
370    init: &'a dyn Fn() -> ItemTreeRc<C>,
371    model: &'a ModelRc<C::Data>,
372}
373
374impl<C: RepeatedItemTree> RepeaterInstanceOps for RustRepeaterOps<'_, C> {
375    fn len(&self) -> usize {
376        self.instances.len()
377    }
378
379    fn splice(&mut self, position: usize, remove: usize, add: usize) {
380        self.instances.splice(
381            position..position + remove,
382            core::iter::repeat_with(|| (RepeatedInstanceState::Dirty, None)).take(add),
383        );
384    }
385
386    fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool {
387        let c = &mut self.instances[instance_idx];
388        if c.0 == RepeatedInstanceState::Dirty {
389            let created = c.1.is_none();
390            if created {
391                c.1 = Some((self.init)());
392            }
393            c.1.as_ref().unwrap().update(row, self.model.row_data(row).unwrap_or_default());
394            c.0 = RepeatedInstanceState::Clean;
395            created
396        } else {
397            false
398        }
399    }
400
401    fn height(&self, instance_idx: usize) -> Option<Coord> {
402        self.instances[instance_idx]
403            .1
404            .as_ref()
405            .map(|x| x.as_pin_ref().item_geometry(0).height_length().get())
406    }
407
408    fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord {
409        let mut y_len = LogicalLength::new(*y);
410        let w = self.instances[instance_idx]
411            .1
412            .as_ref()
413            .unwrap()
414            .as_pin_ref()
415            .listview_layout(&mut y_len);
416        *y = y_len.get();
417        w.get()
418    }
419}
420
421/// This struct is put in a component when using the `for` syntax
422/// It helps instantiating the ItemTree `T`
423#[pin_project]
424pub struct RepeaterTracker<T: RepeatedItemTree> {
425    inner: RefCell<RepeaterInner<T>>,
426    #[pin]
427    model: Property<ModelRc<T::Data>>,
428    #[pin]
429    /// Set to true when the model becomes dirty.
430    is_dirty: Property<bool>,
431    /// Only used for the list view to track if the scrollbar has changed and item needs to be laid out again.
432    #[pin]
433    listview_geometry_tracker: crate::properties::PropertyTracker,
434}
435
436impl<T: RepeatedItemTree> ModelChangeListener for RepeaterTracker<T> {
437    /// Notify the peers that a specific row was changed
438    fn row_changed(self: Pin<&Self>, row: usize) {
439        let mut inner = self.inner.borrow_mut();
440        let inner = &mut *inner;
441        if let Some(c) = inner.instances.get_mut(row.wrapping_sub(inner.layout_state.offset)) {
442            if !self.model.is_dirty() {
443                if let Some(comp) = c.1.as_ref() {
444                    let model = self.project_ref().model.get_untracked();
445                    comp.update(row, model.row_data(row).unwrap_or_default());
446                    c.0 = RepeatedInstanceState::Clean;
447                }
448            } else {
449                c.0 = RepeatedInstanceState::Dirty;
450            }
451        }
452    }
453    /// Notify the peers that rows were added
454    fn row_added(self: Pin<&Self>, mut index: usize, mut count: usize) {
455        let mut inner = self.inner.borrow_mut();
456        if index < inner.layout_state.offset {
457            if index + count <= inner.layout_state.offset {
458                // Entirely before the visible range: shift the offset.
459                inner.layout_state.offset += count;
460                self.is_dirty.set(true);
461                for c in inner.instances.iter_mut() {
462                    c.0 = RepeatedInstanceState::Dirty;
463                }
464                return;
465            }
466            count -= inner.layout_state.offset - index;
467            index = 0;
468        } else {
469            index -= inner.layout_state.offset;
470        }
471        if count == 0 || index > inner.instances.len() {
472            return;
473        }
474        self.is_dirty.set(true);
475        inner.instances.splice(
476            index..index,
477            core::iter::repeat_n((RepeatedInstanceState::Dirty, None), count),
478        );
479        for c in inner.instances[index + count..].iter_mut() {
480            // Because all the indexes are dirty
481            c.0 = RepeatedInstanceState::Dirty;
482        }
483    }
484    /// Notify the peers that rows were removed
485    fn row_removed(self: Pin<&Self>, mut index: usize, mut count: usize) {
486        let mut inner = self.inner.borrow_mut();
487        if index < inner.layout_state.offset {
488            if index + count <= inner.layout_state.offset {
489                // Entirely before the visible range: shift the offset.
490                inner.layout_state.offset -= count;
491                self.is_dirty.set(true);
492                for c in inner.instances.iter_mut() {
493                    c.0 = RepeatedInstanceState::Dirty;
494                }
495                return;
496            }
497            count -= inner.layout_state.offset - index;
498            inner.layout_state.offset = index;
499            index = 0;
500        } else {
501            index -= inner.layout_state.offset;
502        }
503        if count == 0 || index >= inner.instances.len() {
504            return;
505        }
506        if (index + count) > inner.instances.len() {
507            count = inner.instances.len() - index;
508        }
509        self.is_dirty.set(true);
510        inner.instances.drain(index..(index + count));
511        for c in inner.instances[index..].iter_mut() {
512            // Because all the indexes are dirty
513            c.0 = RepeatedInstanceState::Dirty;
514        }
515    }
516
517    fn reset(self: Pin<&Self>) {
518        self.is_dirty.set(true);
519        self.inner.borrow_mut().instances.clear();
520    }
521}
522
523impl<C: RepeatedItemTree> Default for RepeaterTracker<C> {
524    fn default() -> Self {
525        Self {
526            inner: Default::default(),
527            model: Property::new_named(ModelRc::default(), "i_slint_core::Repeater::model"),
528            is_dirty: Property::new_named(false, "i_slint_core::Repeater::is_dirty"),
529            listview_geometry_tracker: Default::default(),
530        }
531    }
532}
533
534#[pin_project]
535pub struct Repeater<C: RepeatedItemTree>(#[pin] ModelChangeListenerContainer<RepeaterTracker<C>>);
536
537impl<C: RepeatedItemTree> Default for Repeater<C> {
538    fn default() -> Self {
539        Self(Default::default())
540    }
541}
542
543impl<C: RepeatedItemTree + 'static> Repeater<C> {
544    fn data(self: Pin<&Self>) -> Pin<&RepeaterTracker<C>> {
545        self.project_ref().0.get()
546    }
547
548    fn model(self: Pin<&Self>) -> ModelRc<C::Data> {
549        let model = self.data().project_ref().model;
550
551        if model.is_dirty() {
552            let old_model = model.get_internal();
553            let m = model.get();
554            if old_model != m {
555                *self.data().inner.borrow_mut() = RepeaterInner::default();
556                self.data().is_dirty.set(true);
557                let peer = self.project_ref().0.model_peer();
558                m.model_tracker().attach_peer(peer);
559            }
560            m
561        } else {
562            model.get()
563        }
564    }
565
566    /// Call this function to make sure that the model is updated.
567    /// The init function is the function to create a ItemTree
568    pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) {
569        let model = self.model();
570        if !self.data().project_ref().is_dirty.get() {
571            return;
572        }
573        let count = model.row_count();
574        let mut inner = self.0.inner.borrow_mut();
575        let offset = inner.layout_state.offset;
576        let mut ops =
577            RustRepeaterOps { instances: &mut inner.instances, init: &init, model: &model };
578        self.data().is_dirty.set(false);
579        let indices_to_init = update_all_instances(&mut ops, offset, count);
580
581        drop(inner);
582        self.init_instances(indices_to_init);
583    }
584
585    fn init_instances(&self, indices: Vec<usize>) {
586        let inner = self.0.inner.borrow();
587        for index in indices {
588            if let Some((_, Some(comp))) = inner.instances.get(index) {
589                comp.init();
590            }
591        }
592    }
593
594    /// Same as `Self::ensure_updated` but for a ListView
595    pub fn ensure_updated_listview(
596        self: Pin<&Self>,
597        init: impl Fn() -> ItemTreeRc<C>,
598        viewport_width: Pin<&Property<LogicalLength>>,
599        viewport_height: Pin<&Property<LogicalLength>>,
600        viewport_y: Pin<&Property<LogicalLength>>,
601        listview_width: LogicalLength,
602        listview_height: Pin<&Property<LogicalLength>>,
603    ) {
604        // Query is_dirty to track model changes
605        let _ = self.data().project_ref().is_dirty.get();
606        self.data().project_ref().is_dirty.set(false);
607
608        let model = self.model();
609        let row_count = model.row_count();
610
611        let data = self.data();
612        let mut inner = data.inner.borrow_mut();
613
614        let RepeaterInner { ref mut instances, ref mut layout_state } = *inner;
615        let mut ops = RustRepeaterOps { instances, init: &init, model: &model };
616        let indices_to_init = update_visible_instances(
617            &mut ops,
618            layout_state,
619            row_count,
620            viewport_width,
621            viewport_height,
622            viewport_y,
623            listview_width,
624            listview_height.get(),
625        );
626
627        drop(inner);
628        self.init_instances(indices_to_init);
629    }
630
631    /// Sets the data directly in the model
632    pub fn model_set_row_data(self: Pin<&Self>, row: usize, data: C::Data) {
633        let model = self.model();
634        model.set_row_data(row, data);
635    }
636
637    /// Set the model binding
638    pub fn set_model_binding(&self, binding: impl Fn() -> ModelRc<C::Data> + 'static) {
639        self.0.model.set_binding(binding);
640    }
641
642    /// Call the visitor for the root of each instance
643    pub fn visit(
644        &self,
645        order: TraversalOrder,
646        mut visitor: crate::item_tree::ItemVisitorRefMut,
647    ) -> crate::item_tree::VisitChildrenResult {
648        // We can't keep self.inner borrowed because the event might modify the model
649        let count = self.0.inner.borrow().instances.len() as u32;
650        for i in 0..count {
651            let i = if order == TraversalOrder::BackToFront { i } else { count - i - 1 };
652            let c = self.0.inner.borrow().instances.get(i as usize).and_then(|c| c.1.clone());
653            if let Some(c) = c
654                && c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
655            {
656                return crate::item_tree::VisitChildrenResult::abort(i, 0);
657            }
658        }
659        crate::item_tree::VisitChildrenResult::CONTINUE
660    }
661
662    /// Return the amount of instances currently in the repeater
663    pub fn len(&self) -> usize {
664        self.0.inner.borrow().instances.len()
665    }
666
667    /// Return the range of indices used by this Repeater.
668    ///
669    /// Two values are necessary here since the Repeater can start to insert the data from its
670    /// model at an offset.
671    pub fn range(&self) -> core::ops::Range<usize> {
672        let inner = self.0.inner.borrow();
673        core::ops::Range {
674            start: inner.layout_state.offset,
675            end: inner.layout_state.offset + inner.instances.len(),
676        }
677    }
678
679    /// Return the instance for the given model index.
680    /// The index should be within [`Self::range()`]
681    pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
682        let inner = self.0.inner.borrow();
683        inner
684            .instances
685            .get(index.checked_sub(inner.layout_state.offset)?)
686            .map(|c| c.1.clone().expect("That was updated before!"))
687    }
688
689    /// Return true if the Repeater as empty
690    pub fn is_empty(&self) -> bool {
691        self.len() == 0
692    }
693
694    /// Returns a vector containing all instances
695    pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
696        self.0.inner.borrow().instances.iter().flat_map(|x| x.1.clone()).collect()
697    }
698}
699
700#[pin_project]
701pub struct Conditional<C: RepeatedItemTree> {
702    #[pin]
703    model: Property<bool>,
704    instance: RefCell<Option<ItemTreeRc<C>>>,
705}
706
707impl<C: RepeatedItemTree> Default for Conditional<C> {
708    fn default() -> Self {
709        Self {
710            model: Property::new_named(false, "i_slint_core::Conditional::model"),
711            instance: RefCell::new(None),
712        }
713    }
714}
715
716impl<C: RepeatedItemTree + 'static> Conditional<C> {
717    /// Call this function to make sure that the model is updated.
718    /// The init function is the function to create a ItemTree
719    pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) {
720        let model = self.project_ref().model.get();
721
722        if !model {
723            drop(self.instance.replace(None));
724        } else if self.instance.borrow().is_none() {
725            let i = init();
726            self.instance.replace(Some(i.clone()));
727            i.init();
728        }
729    }
730
731    /// Set the model binding
732    pub fn set_model_binding(&self, binding: impl Fn() -> bool + 'static) {
733        self.model.set_binding(binding);
734    }
735
736    /// Call the visitor for the root of each instance
737    pub fn visit(
738        &self,
739        order: TraversalOrder,
740        mut visitor: crate::item_tree::ItemVisitorRefMut,
741    ) -> crate::item_tree::VisitChildrenResult {
742        // We can't keep self.inner borrowed because the event might modify the model
743        let instance = self.instance.borrow().clone();
744        if let Some(c) = instance
745            && c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
746        {
747            return crate::item_tree::VisitChildrenResult::abort(0, 0);
748        }
749
750        crate::item_tree::VisitChildrenResult::CONTINUE
751    }
752
753    /// Return the amount of instances (1 if the conditional is active, 0 otherwise)
754    pub fn len(&self) -> usize {
755        self.instance.borrow().is_some() as usize
756    }
757
758    /// Return the range of indices used by this Conditional.
759    ///
760    /// Similar to Repeater::range, but the range is always [0, 1] if the Conditional is active.
761    pub fn range(&self) -> core::ops::Range<usize> {
762        0..self.len()
763    }
764
765    /// Return the instance for the given model index.
766    /// The index should be within [`Self::range()`]
767    pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
768        if index != 0 {
769            return None;
770        }
771        self.instance.borrow().clone()
772    }
773
774    /// Return true if the Repeater as empty
775    pub fn is_empty(&self) -> bool {
776        self.len() == 0
777    }
778
779    /// Returns a vector containing all instances
780    pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
781        self.instance.borrow().clone().into_iter().collect()
782    }
783}
784
785#[cfg(feature = "ffi")]
786mod ffi {
787    #![allow(unsafe_code)]
788
789    use super::*;
790
791    /// C++ callback table for [`RepeaterInstanceOps`], including the opaque
792    /// user_data pointer that is passed to each callback.
793    #[repr(C)]
794    pub struct RepeaterInstanceOpsVTable {
795        pub user_data: *mut core::ffi::c_void,
796        pub len: unsafe extern "C" fn(user_data: *mut core::ffi::c_void) -> usize,
797        pub splice: unsafe extern "C" fn(
798            user_data: *mut core::ffi::c_void,
799            position: usize,
800            remove: usize,
801            add: usize,
802        ),
803        pub ensure_updated: unsafe extern "C" fn(
804            user_data: *mut core::ffi::c_void,
805            instance_idx: usize,
806            row: usize,
807        ) -> bool,
808        /// Height of instance, or NaN if not yet created.
809        pub height:
810            unsafe extern "C" fn(user_data: *mut core::ffi::c_void, instance_idx: usize) -> Coord,
811        pub listview_layout: Option<
812            unsafe extern "C" fn(
813                user_data: *mut core::ffi::c_void,
814                instance_idx: usize,
815                y: &mut Coord,
816            ) -> Coord,
817        >,
818        pub init: unsafe extern "C" fn(user_data: *mut core::ffi::c_void, instance_idx: usize),
819    }
820
821    impl RepeaterInstanceOpsVTable {
822        fn init_instances(&self, indices: Vec<usize>) {
823            for idx in indices {
824                unsafe { (self.init)(self.user_data, idx) };
825            }
826        }
827    }
828
829    impl RepeaterInstanceOps for RepeaterInstanceOpsVTable {
830        fn len(&self) -> usize {
831            unsafe { (self.len)(self.user_data) }
832        }
833        fn splice(&mut self, position: usize, remove: usize, add: usize) {
834            unsafe { (self.splice)(self.user_data, position, remove, add) }
835        }
836        fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool {
837            unsafe { (self.ensure_updated)(self.user_data, instance_idx, row) }
838        }
839        fn height(&self, instance_idx: usize) -> Option<Coord> {
840            let h = unsafe { (self.height)(self.user_data, instance_idx) };
841            if h.is_nan() { None } else { Some(h) }
842        }
843        fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord {
844            self.listview_layout
845                .map_or(0 as Coord, |f| unsafe { f(self.user_data, instance_idx, y) })
846        }
847    }
848
849    #[unsafe(no_mangle)]
850    pub extern "C" fn slint_repeater_ensure_updated(
851        ops: &mut RepeaterInstanceOpsVTable,
852        offset: usize,
853        count: usize,
854    ) {
855        let indices_to_init = update_all_instances(ops, offset, count);
856        ops.init_instances(indices_to_init);
857    }
858
859    #[unsafe(no_mangle)]
860    pub extern "C" fn slint_repeater_ensure_updated_listview(
861        ops: &mut RepeaterInstanceOpsVTable,
862        state: &mut RepeaterLayoutState,
863        row_count: usize,
864        viewport_width: Pin<&Property<LogicalLength>>,
865        viewport_height: Pin<&Property<LogicalLength>>,
866        viewport_y: Pin<&Property<LogicalLength>>,
867        listview_width: LogicalLength,
868        listview_height: LogicalLength,
869    ) {
870        let indices_to_init = update_visible_instances(
871            ops,
872            state,
873            row_count,
874            viewport_width,
875            viewport_height,
876            viewport_y,
877            listview_width,
878            listview_height,
879        );
880        ops.init_instances(indices_to_init);
881    }
882}