kas_core/core/
collection.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! The [`Collection`] trait
7
8use crate::layout::{GridCellInfo, GridDimensions};
9use crate::{Node, Tile, Widget};
10use kas_macros::impl_self;
11use std::ops::RangeBounds;
12
13/// A collection of (child) widgets
14///
15/// Essentially, a `Collection` is a list of widgets. Notable implementations are:
16///
17/// -   Slices `[W]` where `W: Widget`
18/// -   Arrays `[W; N]` where `W: Widget` and `const N: usize`
19/// -   [`Vec`]`<W>` where `W: Widget`
20/// -   The output of [`kas::collection!`]. This macro constructs an anonymous
21///     struct of widgets which implements `Collection`.
22pub trait Collection {
23    /// The associated data type
24    type Data;
25
26    /// True if the collection is empty
27    fn is_empty(&self) -> bool {
28        self.len() == 0
29    }
30
31    /// The number of widgets
32    fn len(&self) -> usize;
33
34    /// Get a widget as a [`Tile`]
35    fn get_tile(&self, index: usize) -> Option<&dyn Tile>;
36
37    /// Get a widget as a mutable [`Tile`]
38    fn get_mut_tile(&mut self, index: usize) -> Option<&mut dyn Tile>;
39
40    /// Operate on a widget as a [`Node`]
41    fn child_node<'n>(&'n mut self, data: &'n Self::Data, index: usize) -> Option<Node<'n>>;
42
43    /// Iterate over elements as [`Tile`] items within `range`
44    ///
45    /// Note: there is currently no mutable equivalent due to the streaming
46    /// iterator problem.
47    fn iter_tile(&self, range: impl RangeBounds<usize>) -> CollectionIterTile<'_, Self> {
48        use std::ops::Bound::{Excluded, Included, Unbounded};
49        let start = match range.start_bound() {
50            Included(start) => *start,
51            Excluded(start) => *start + 1,
52            Unbounded => 0,
53        };
54        let end = match range.end_bound() {
55            Included(end) => *end + 1,
56            Excluded(end) => *end,
57            Unbounded => self.len(),
58        };
59        CollectionIterTile {
60            start,
61            end,
62            collection: self,
63        }
64    }
65
66    /// Binary searches this collection with a comparator function.
67    ///
68    /// Similar to [`slice::binary_search_by`][<[()]>::binary_search_by], the
69    /// comparator function should return whether the element is `Less` than,
70    /// `Equal` to, or `Greater` than the desired target, and the collection
71    /// should be sorted by this comparator (if not, the result is meaningless).
72    ///
73    /// Returns:
74    ///
75    /// -   `Some(Ok(index))` if an `Equal` element is found at `index`
76    /// -   `Some(Err(index))` if no `Equal` element is found; in this case such
77    ///     an element could be inserted at `index`
78    /// -   `None` if [`Collection::get_tile`] returns `None` for some
79    ///     `index` less than [`Collection::len`]. This is an error case that
80    ///     should not occur.
81    fn binary_search_by<'a, F>(&'a self, mut f: F) -> Option<Result<usize, usize>>
82    where
83        F: FnMut(&'a dyn Tile) -> std::cmp::Ordering,
84    {
85        use std::cmp::Ordering::{Greater, Less};
86
87        // INVARIANTS:
88        // - 0 <= left <= left + size = right <= self.len()
89        // - f returns Less for everything in self[..left]
90        // - f returns Greater for everything in self[right..]
91        let mut size = self.len();
92        let mut left = 0;
93        let mut right = size;
94        while left < right {
95            let mid = left + size / 2;
96
97            let cmp = f(self.get_tile(mid)?);
98
99            if cmp == Less {
100                left = mid + 1;
101            } else if cmp == Greater {
102                right = mid;
103            } else {
104                return Some(Ok(mid));
105            }
106
107            size = right - left;
108        }
109
110        Some(Err(left))
111    }
112}
113
114/// A collection with attached cell info
115pub trait CellCollection: Collection {
116    /// Get row/column info associated with cell at `index`
117    fn cell_info(&self, index: usize) -> Option<GridCellInfo>;
118
119    /// Iterate over [`GridCellInfo`] of elements within `range`
120    fn iter_cell_info(&self, range: impl RangeBounds<usize>) -> CollectionIterCellInfo<'_, Self> {
121        use std::ops::Bound::{Excluded, Included, Unbounded};
122        let start = match range.start_bound() {
123            Included(start) => *start,
124            Excluded(start) => *start + 1,
125            Unbounded => 0,
126        };
127        let end = match range.end_bound() {
128            Included(end) => *end + 1,
129            Excluded(end) => *end,
130            Unbounded => self.len(),
131        };
132        CollectionIterCellInfo {
133            start,
134            end,
135            collection: self,
136        }
137    }
138
139    /// Get or calculate grid dimension info
140    ///
141    /// The default implementation calculates this from [`Self::cell_info`].
142    fn grid_dimensions(&self) -> GridDimensions {
143        let mut dim = GridDimensions::default();
144        let (mut last_col, mut last_row) = (0, 0);
145        for cell_info in self.iter_cell_info(..) {
146            last_col = last_col.max(cell_info.last_col);
147            last_row = last_row.max(cell_info.last_row);
148            if cell_info.last_col > cell_info.col {
149                dim.col_spans += 1;
150            }
151            if cell_info.last_row > cell_info.row {
152                dim.row_spans += 1;
153            }
154        }
155        dim.cols = last_col + 1;
156        dim.rows = last_row + 1;
157        dim
158    }
159}
160
161#[impl_self]
162mod CollectionIterTile {
163    /// An iterator over a [`Collection`] as [`Tile`] elements
164    pub struct CollectionIterTile<'a, C: Collection + ?Sized> {
165        start: usize,
166        end: usize,
167        collection: &'a C,
168    }
169
170    impl Iterator for Self {
171        type Item = &'a dyn Tile;
172
173        fn next(&mut self) -> Option<Self::Item> {
174            let index = self.start;
175            if index < self.end {
176                self.start += 1;
177                self.collection.get_tile(index)
178            } else {
179                None
180            }
181        }
182    }
183
184    impl DoubleEndedIterator for Self {
185        fn next_back(&mut self) -> Option<Self::Item> {
186            if self.start < self.end {
187                let index = self.end - 1;
188                self.end = index;
189                self.collection.get_tile(index)
190            } else {
191                None
192            }
193        }
194    }
195
196    impl ExactSizeIterator for Self {}
197}
198
199#[impl_self]
200mod CollectionIterCellInfo {
201    /// An iterator over a [`Collection`] as [`GridCellInfo`] elements
202    pub struct CollectionIterCellInfo<'a, C: CellCollection + ?Sized> {
203        start: usize,
204        end: usize,
205        collection: &'a C,
206    }
207
208    impl Iterator for Self {
209        type Item = GridCellInfo;
210
211        fn next(&mut self) -> Option<Self::Item> {
212            let index = self.start;
213            if index < self.end {
214                self.start += 1;
215                self.collection.cell_info(index)
216            } else {
217                None
218            }
219        }
220    }
221
222    impl DoubleEndedIterator for Self {
223        fn next_back(&mut self) -> Option<Self::Item> {
224            if self.start < self.end {
225                let index = self.end - 1;
226                self.end = index;
227                self.collection.cell_info(index)
228            } else {
229                None
230            }
231        }
232    }
233
234    impl ExactSizeIterator for Self {}
235}
236
237macro_rules! impl_slice {
238    (($($gg:tt)*) for $t:ty as $w:ident in $pat:pat) => {
239        impl<$($gg)*> Collection for $t {
240            type Data = W::Data;
241
242            #[inline]
243            fn len(&self) -> usize {
244                <[_]>::len(self)
245            }
246
247            #[inline]
248            fn get_tile(&self, index: usize) -> Option<&dyn Tile> {
249                self.get(index).map(|$pat| $w as &dyn Tile)
250            }
251
252            #[inline]
253            fn get_mut_tile(&mut self, index: usize) -> Option<&mut dyn Tile> {
254                self.get_mut(index).map(|$pat| $w as &mut dyn Tile)
255            }
256
257            #[inline]
258            fn child_node<'n>(
259                &'n mut self,
260                data: &'n Self::Data,
261                index: usize,
262            ) -> Option<Node<'n>> {
263                self.get_mut(index).map(|$pat| $w.as_node(data))
264            }
265
266            #[inline]
267            fn binary_search_by<'a, F>(&'a self, mut f: F) -> Option<Result<usize, usize>>
268            where
269                F: FnMut(&'a dyn Tile) -> std::cmp::Ordering,
270            {
271                Some(<[_]>::binary_search_by(self, move |$pat| f($w.as_tile())))
272            }
273        }
274    };
275}
276
277// NOTE: If Rust had better lifetime analysis we could replace
278// the following impls with a single one:
279// impl<W: Widget, T: std::ops::Deref<Target = [W]> + ?Sized> Collection for T
280impl_slice!((const N: usize, W: Widget) for [W; N] as w in w);
281impl_slice!((W: Widget) for [W] as w in w);
282impl_slice!((W: Widget) for Vec<W> as w in w);
283
284impl_slice!((const N: usize, W: Widget) for [(GridCellInfo, W); N] as w in (_, w));
285impl_slice!((W: Widget) for [(GridCellInfo, W)] as w in (_, w));
286impl_slice!((W: Widget) for Vec<(GridCellInfo, W)> as w in (_, w));
287
288impl<W: Widget, C: Collection> CellCollection for C
289where
290    C: std::ops::Deref<Target = [(GridCellInfo, W)]>,
291{
292    fn cell_info(&self, index: usize) -> Option<GridCellInfo> {
293        self.get(index).map(|(info, _)| *info)
294    }
295}