waterui_core/
views.rs

1//! Collection of view-related utilities for managing and transforming UI components.
2//!
3//! This module provides types and traits for working with collections of views in a type-safe
4//! and efficient manner. It includes utilities for type erasure, transformation, and identity
5//! tracking of view collections.
6
7use crate::id::Id as RawId;
8use crate::{AnyView, View};
9use alloc::fmt::Debug;
10use alloc::{boxed::Box, collections::BTreeMap, rc::Rc, vec::Vec};
11use core::any::type_name;
12use core::num::NonZeroI32;
13use core::ops::{Bound, RangeBounds};
14use core::{
15    cell::{Cell, RefCell},
16    hash::Hash,
17};
18use nami::collection::Collection;
19use nami::watcher::{BoxWatcherGuard, Context, WatcherGuard};
20
21use crate::id::{Identifiable, SelfId};
22
23/// A trait for collections that can provide unique identifiers for their elements.
24///
25/// `Views` extends the `Collection` trait by adding identity tracking capabilities.
26/// This allows for efficient diffing and reconciliation of UI elements during updates.
27/// Tip: the `get` method of `Collection` should return a unique identifier for each item.
28pub trait Views {
29    /// The type of unique identifier for items in the collection.
30    /// Must implement `Hash` and `Ord` to ensure uniqueness and ordering.
31    type Id: 'static + Hash + Ord + Clone;
32    /// The type of guard returned when registering a watcher.
33    type Guard: WatcherGuard;
34    /// The view type that this collection produces for each element.
35    type View: View;
36    /// Returns the unique identifier for the item at the specified index, or `None` if out of bounds.
37    fn get_id(&self, index: usize) -> Option<Self::Id>;
38    /// Returns the number of items in the collection.
39    fn len(&self) -> usize;
40
41    /// Returns `true` if the collection contains no elements.
42    fn is_empty(&self) -> bool {
43        self.len() == 0
44    }
45
46    /// Registers a watcher for changes in the specified range of the collection.
47    ///
48    /// Returns a guard that will unregister the watcher when dropped.
49    fn watch(
50        &self,
51        range: impl RangeBounds<usize>,
52        watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
53    ) -> Self::Guard;
54
55    /// Returns the view at the specified index, or `None` if the index is out of bounds.
56    fn get_view(&self, id: usize) -> Option<Self::View>;
57}
58
59/// A type-erased container for `Views` collections.
60///
61/// `AnyViews` provides a uniform interface to different views collections
62/// by wrapping them in a type-erased container. This enables working with
63/// heterogeneous view collections through a common interface.
64pub struct AnyViews<V>(Box<dyn AnyViewsImpl<View = V>>);
65
66impl<V> Debug for AnyViews<V> {
67    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68        f.write_str(type_name::<Self>())
69    }
70}
71
72/// A reference-counted, type-erased container for `Views` collections.
73/// `SharedAnyViews` allows multiple owners to share access
74/// to the same views collection through reference counting.
75pub struct SharedAnyViews<V>(Rc<dyn AnyViewsImpl<View = V>>);
76
77impl<V> Clone for SharedAnyViews<V> {
78    fn clone(&self) -> Self {
79        Self(self.0.clone())
80    }
81}
82
83impl<V> SharedAnyViews<V> {
84    /// Creates a new type-erased shared view collection from any type implementing the `Views` trait.
85    ///
86    /// This function wraps the provided collection in a type-erased container using reference counting, allowing
87    /// different view collection implementations to be used through a common interface with shared ownership.
88    ///
89    /// # Parameters
90    /// * `contents` - Any collection implementing the `Views` trait with the appropriate item type
91    ///
92    /// # Returns
93    /// A new `SharedAnyViews` instance containing the provided collection
94    pub fn new(contents: impl Views<View = V> + 'static) -> Self {
95        Self(Rc::new(IntoAnyViews::new(contents)))
96    }
97}
98
99impl<V> From<AnyViews<V>> for SharedAnyViews<V> {
100    fn from(value: AnyViews<V>) -> Self {
101        Self(Rc::from(value.0))
102    }
103}
104
105impl<V> Debug for SharedAnyViews<V> {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        f.write_str(type_name::<Self>())
108    }
109}
110
111impl<V: View> Views for SharedAnyViews<V> {
112    type Id = SelfId<RawId>;
113    type Guard = BoxWatcherGuard;
114    type View = V;
115    fn get_id(&self, index: usize) -> Option<Self::Id> {
116        self.0.get_id(index).map(SelfId::new)
117    }
118    fn get_view(&self, index: usize) -> Option<Self::View> {
119        self.0.get_view(index)
120    }
121    fn len(&self) -> usize {
122        self.0.len()
123    }
124
125    fn watch(
126        &self,
127        range: impl RangeBounds<usize>,
128        watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
129    ) -> Self::Guard {
130        self.0.watch(
131            (range.start_bound().cloned(), range.end_bound().cloned()),
132            Box::new(move |ctx| {
133                let ctx =
134                    ctx.map(|value| value.iter().copied().map(SelfId::new).collect::<Vec<_>>());
135                watcher(ctx.as_deref());
136            }),
137        )
138    }
139}
140
141trait AnyViewsImpl {
142    type View;
143
144    fn get_view(&self, index: usize) -> Option<Self::View>;
145    fn get_id(&self, index: usize) -> Option<RawId>;
146    fn len(&self) -> usize;
147    #[allow(clippy::type_complexity)]
148    fn watch(
149        &self,
150        range: (Bound<usize>, Bound<usize>),
151        watcher: Box<dyn for<'a> Fn(Context<&'a [RawId]>) + 'static>,
152    ) -> BoxWatcherGuard;
153}
154
155#[derive(Debug)]
156struct IdGenerator<Id> {
157    map: RefCell<BTreeMap<Id, i32>>,
158    counter: Cell<i32>,
159}
160
161impl<Id: Hash + Ord> Default for IdGenerator<Id> {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl<Id: Hash + Ord> IdGenerator<Id> {
168    pub const fn new() -> Self {
169        Self {
170            map: RefCell::new(BTreeMap::new()),
171            counter: Cell::new(i32::MIN),
172        }
173    }
174    pub fn to_id(&self, value: Id) -> RawId {
175        let mut this = self.map.borrow_mut();
176        if let Some(&id) = this.get(&value) {
177            return RawId::from(unsafe { NonZeroI32::new_unchecked(id) });
178        }
179        let id = self.counter.get();
180        self.counter
181            .set(id.checked_add(1).expect("id counter should not overflow"));
182        this.insert(value, id);
183        RawId::from(unsafe { NonZeroI32::new_unchecked(id) })
184    }
185}
186
187struct IntoAnyViews<V>
188where
189    V: Views,
190{
191    contents: V,
192    id: Rc<IdGenerator<V::Id>>,
193}
194
195impl<V> IntoAnyViews<V>
196where
197    V: Views + 'static,
198{
199    pub fn new(contents: V) -> Self {
200        Self {
201            contents,
202            id: Rc::default(),
203        }
204    }
205}
206
207impl<V> AnyViewsImpl for IntoAnyViews<V>
208where
209    V: Views + 'static,
210{
211    type View = V::View;
212
213    fn get_view(&self, index: usize) -> Option<Self::View> {
214        self.contents.get_view(index)
215    }
216
217    fn get_id(&self, index: usize) -> Option<RawId> {
218        self.contents.get_id(index).map(|item| self.id.to_id(item))
219    }
220
221    fn len(&self) -> usize {
222        self.contents.len()
223    }
224
225    fn watch(
226        &self,
227        range: (Bound<usize>, Bound<usize>),
228        watcher: Box<dyn for<'a> Fn(Context<&'a [RawId]>) + 'static>,
229    ) -> BoxWatcherGuard {
230        let id = self.id.clone();
231        Box::new(self.contents.watch(range, move |ctx| {
232            let ctx = ctx.map(|value| {
233                value
234                    .iter()
235                    .map(|data| id.to_id(data.clone()))
236                    .collect::<Vec<_>>()
237            });
238            watcher(ctx.as_deref());
239        }))
240    }
241}
242impl<V> AnyViews<V>
243where
244    V: View,
245{
246    /// Creates a new type-erased view collection from any type implementing the `Views` trait.
247    ///
248    /// This function wraps the provided collection in a type-erased container, allowing
249    /// different view collection implementations to be used through a common interface.
250    ///
251    /// # Parameters
252    /// * `contents` - Any collection implementing the `Views` trait with the appropriate item type
253    ///
254    /// # Returns
255    /// A new `AnyViews` instance containing the provided collection
256    pub fn new<C>(contents: C) -> Self
257    where
258        C: Views<View = V> + 'static,
259    {
260        Self(Box::new(IntoAnyViews {
261            id: Rc::new(IdGenerator::<C::Id>::new()),
262            contents,
263        }))
264    }
265}
266
267impl<V> Views for AnyViews<V>
268where
269    V: View,
270{
271    type Id = SelfId<RawId>;
272    type Guard = BoxWatcherGuard;
273    type View = V;
274    fn get_id(&self, index: usize) -> Option<Self::Id> {
275        self.0.get_id(index).map(SelfId::new)
276    }
277    fn get_view(&self, index: usize) -> Option<Self::View> {
278        self.0.get_view(index)
279    }
280    fn len(&self) -> usize {
281        self.0.len()
282    }
283
284    fn watch(
285        &self,
286        range: impl RangeBounds<usize>,
287        watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
288    ) -> Self::Guard {
289        self.0.watch(
290            (range.start_bound().cloned(), range.end_bound().cloned()),
291            Box::new(move |ctx| {
292                let ctx =
293                    ctx.map(|value| value.iter().copied().map(SelfId::new).collect::<Vec<_>>());
294                watcher(ctx.as_deref());
295            }),
296        )
297    }
298}
299/// A utility for transforming elements of a collection with a mapping function.
300///
301/// `ForEach` applies a transformation function to each element of a source collection,
302/// producing a new collection with the transformed elements. This is useful for
303/// transforming data models into view representations.
304#[derive(Debug, Clone)]
305pub struct ForEach<C, F, V>
306where
307    C: Collection,
308    C::Item: Identifiable,
309    F: Fn(C::Item) -> V,
310    V: View,
311{
312    data: C,
313    generator: F,
314}
315
316impl<C, F, V> ForEach<C, F, V>
317where
318    C: Collection,
319    C::Item: Identifiable,
320    F: Fn(C::Item) -> V,
321    V: View,
322{
323    /// Creates a new `ForEach` transformation with the provided data collection and generator function.
324    ///
325    /// # Parameters
326    /// * `data` - The source collection containing elements to be transformed
327    /// * `generator` - A function that transforms elements from the source collection
328    ///
329    /// # Returns
330    /// A new `ForEach` instance that will apply the transformation when accessed
331    pub const fn new(data: C, generator: F) -> Self {
332        Self { data, generator }
333    }
334
335    /// Consumes the `ForEach` and returns the original data collection and generator function.
336    ///
337    /// # Returns
338    /// A tuple containing the original data collection and generator function
339    pub fn into_inner(self) -> (C, F) {
340        (self.data, self.generator)
341    }
342}
343
344/// Represents a single transformed item, pairing data with a generator function to produce a view.
345#[derive(Debug)]
346#[allow(dead_code)]
347pub struct ForEachItem<T, F, V>
348where
349    F: Fn(T) -> V,
350    V: View,
351{
352    data: T,
353
354    generator: Rc<F>,
355}
356
357impl<C, F, V> Collection for ForEach<C, F, V>
358where
359    C: Collection,
360    C::Item: Identifiable,
361    F: 'static + Fn(C::Item) -> V,
362    V: View,
363{
364    type Item = <C::Item as Identifiable>::Id;
365    type Guard = C::Guard;
366    fn get(&self, index: usize) -> Option<Self::Item> {
367        self.data.get(index).map(|item| item.id())
368    }
369
370    fn len(&self) -> usize {
371        self.data.len()
372    }
373
374    fn watch(
375        &self,
376        range: impl RangeBounds<usize>,
377        watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
378    ) -> Self::Guard {
379        self.data.watch(range, move |ctx| {
380            let ctx = ctx.map(|value| {
381                value
382                    .iter()
383                    .map(super::id::Identifiable::id)
384                    .collect::<Vec<_>>()
385            });
386
387            watcher(ctx.as_deref());
388        })
389    }
390}
391
392impl<C, F, V> Views for ForEach<C, F, V>
393where
394    C: Collection,
395    C::Item: Identifiable,
396    F: 'static + Fn(C::Item) -> V,
397    V: View,
398{
399    type Id = <C::Item as Identifiable>::Id;
400    type View = V;
401    type Guard = C::Guard;
402    fn get_id(&self, index: usize) -> Option<Self::Id> {
403        self.data.get(index).map(|item| item.id())
404    }
405    fn get_view(&self, index: usize) -> Option<Self::View> {
406        self.data.get(index).map(|item| (self.generator)(item))
407    }
408    fn len(&self) -> usize {
409        self.data.len()
410    }
411    fn watch(
412        &self,
413        range: impl RangeBounds<usize>,
414        watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
415    ) -> Self::Guard {
416        self.data.watch(range, move |ctx| {
417            let ctx = ctx.map(|value| {
418                value
419                    .iter()
420                    .map(super::id::Identifiable::id)
421                    .collect::<Vec<_>>()
422            });
423
424            watcher(ctx.as_deref());
425        })
426    }
427}
428
429/// A statically sized collection that never changes, removes, or adds items.
430///
431/// `Constant` wraps a collection and provides a stable view collection where
432/// elements are identified by their index position. This is useful for static
433/// lists of views that don't need to be updated or reordered.
434#[derive(Debug, Clone)]
435pub struct Constant<C>
436where
437    C: Collection,
438    C::Item: View,
439{
440    value: C,
441}
442
443impl<C> Constant<C>
444where
445    C: Collection,
446    C::Item: View,
447{
448    /// Creates a new `Constant` collection from the provided collection.
449    ///
450    /// # Parameters
451    /// * `value` - The underlying collection to be wrapped as a constant view collection
452    ///
453    /// # Returns
454    /// A new `Constant` instance containing the provided collection
455    pub const fn new(value: C) -> Self {
456        Self { value }
457    }
458}
459
460impl<C> Collection for Constant<C>
461where
462    C: Collection + Clone,
463    C::Item: View,
464{
465    type Item = SelfId<usize>;
466    type Guard = ();
467
468    fn get(&self, index: usize) -> Option<Self::Item> {
469        if index < self.value.len() {
470            Some(SelfId::new(index))
471        } else {
472            None
473        }
474    }
475
476    fn len(&self) -> usize {
477        self.value.len()
478    }
479
480    fn watch(
481        &self,
482        _range: impl RangeBounds<usize>,
483        _watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
484    ) -> Self::Guard {
485    }
486}
487
488impl<V> Views for Constant<V>
489where
490    V: Collection + Clone,
491    V::Item: View,
492{
493    type Id = SelfId<usize>;
494    type Guard = ();
495    type View = V::Item;
496
497    fn len(&self) -> usize {
498        self.value.len()
499    }
500
501    fn get_id(&self, index: usize) -> Option<Self::Id> {
502        if index < self.value.len() {
503            Some(SelfId::new(index))
504        } else {
505            None
506        }
507    }
508
509    fn get_view(&self, index: usize) -> Option<Self::View> {
510        self.value.get(index)
511    }
512
513    fn watch(
514        &self,
515        _range: impl RangeBounds<usize>,
516        _watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
517    ) -> Self::Guard {
518        // No-op for Constant
519    }
520}
521
522impl<V: View + Clone> Views for Vec<V> {
523    type Id = SelfId<usize>;
524    type Guard = ();
525    type View = V;
526
527    fn len(&self) -> usize {
528        self.len()
529    }
530
531    fn get_id(&self, index: usize) -> Option<Self::Id> {
532        if index < self.len() {
533            Some(SelfId::new(index))
534        } else {
535            None
536        }
537    }
538
539    fn get_view(&self, index: usize) -> Option<Self::View> {
540        self.get(index)
541    }
542
543    fn watch(
544        &self,
545        _range: impl RangeBounds<usize>,
546        _watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
547    ) -> Self::Guard {
548        // No-op for Vec
549    }
550}
551
552impl<V: View + Clone, const N: usize> Views for [V; N] {
553    type Id = SelfId<usize>;
554    type Guard = ();
555    type View = V;
556
557    fn len(&self) -> usize {
558        self.as_ref().len()
559    }
560
561    fn get_id(&self, index: usize) -> Option<Self::Id> {
562        if index < self.as_ref().len() {
563            Some(SelfId::new(index))
564        } else {
565            None
566        }
567    }
568
569    fn get_view(&self, index: usize) -> Option<Self::View> {
570        self.get(index)
571    }
572
573    fn watch(
574        &self,
575        _range: impl RangeBounds<usize>,
576        _watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
577    ) -> Self::Guard {
578        // No-op for arrays
579    }
580}
581
582/// A view collection that transforms views from a source collection using a mapping function.
583///
584/// `Map` wraps an existing view collection and applies a transformation function to each
585/// view when it is accessed, allowing for lazy transformation of views.
586#[derive(Debug, Clone)]
587pub struct Map<C, F> {
588    source: C,
589    f: F,
590}
591
592impl<C, F, V> Map<C, F>
593where
594    C: Views,
595    F: Fn(C::View) -> V,
596    V: View,
597{
598    /// Creates a new `Map` that transforms views from the source collection.
599    ///
600    /// # Parameters
601    /// * `source` - The source view collection to map over
602    /// * `f` - The transformation function to apply to each view
603    ///
604    /// # Returns
605    /// A new `Map` instance that will apply the transformation to views
606    #[must_use]
607    pub const fn new(source: C, f: F) -> Self {
608        Self { source, f }
609    }
610}
611
612impl<C, F, V> Views for Map<C, F>
613where
614    C: Views,
615    F: Clone + Fn(C::View) -> V,
616    V: View,
617{
618    type Id = C::Id;
619    type Guard = C::Guard;
620    type View = V;
621
622    fn len(&self) -> usize {
623        self.source.len()
624    }
625
626    fn get_id(&self, index: usize) -> Option<Self::Id> {
627        self.source.get_id(index)
628    }
629
630    fn get_view(&self, index: usize) -> Option<Self::View> {
631        self.source.get_view(index).map(&self.f)
632    }
633
634    fn watch(
635        &self,
636        range: impl RangeBounds<usize>,
637        watcher: impl for<'a> Fn(Context<&'a [Self::Id]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
638    ) -> Self::Guard {
639        self.source.watch(range, watcher)
640    }
641}
642
643/// Extension trait providing additional utilities for types implementing `Views`.
644///
645/// This trait provides convenient methods for transforming and manipulating view collections,
646/// such as mapping views to different types.
647pub trait ViewsExt: Views {
648    /// Transforms each view in the collection using the provided mapping function.
649    ///
650    /// # Parameters
651    /// * `f` - A function that transforms each view from the source type to a new view type
652    ///
653    /// # Returns
654    /// A new `Map` view collection that applies the transformation to each element
655    fn map<F, V>(self, f: F) -> Map<Self, F>
656    where
657        Self: Sized,
658        F: Fn(Self::View) -> V,
659        V: View,
660    {
661        Map::new(self, f)
662    }
663
664    /// Erases the specific type of the view collection, returning a type-erased `AnyViews`.
665    fn erase(self) -> AnyViews<AnyView>
666    where
667        Self: 'static + Sized,
668    {
669        AnyViews::new(self.map(AnyView::new))
670    }
671}
672
673impl<T: Views> ViewsExt for T {}