Skip to main content

kas_widgets/
flow.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//! A row or column with wrapping
7
8use crate::List;
9use kas::Collection;
10use kas::layout::{FlowSetter, FlowSolver, FlowStorage, RulesSetter, RulesSolver};
11use kas::prelude::*;
12use std::ops::{Index, IndexMut};
13
14#[impl_self]
15mod Flow {
16    /// Rows or columns of content with line-splitting
17    ///
18    /// This widget is a variant of [`List`], arranging a linear [`Collection`]
19    /// of children into multiple rows or columns with automatic splitting.
20    /// Unlike [`Grid`](crate::Grid), items are not aligned across lines.
21    ///
22    /// When the collection uses [`Vec`], various methods to insert/remove
23    /// elements are available.
24    ///
25    /// ## Layout details
26    ///
27    /// Currently only horizontal lines (rows) which wrap down to the next line
28    /// are supported.
29    ///
30    /// Width requirements depend on the desired numbers of columns; see
31    /// [`Self::set_num_columns`].
32    ///
33    /// Items within each line are stretched (if any has non-zero [`Stretch`]
34    /// priority) in accordance with [`SizeRules::solve_widths`]. It is not
35    /// currently possible to adjust this (except by tweaking the stretchiness
36    /// of items).
37    ///
38    /// ## Performance
39    ///
40    /// Sizing, drawing and event handling are all `O(n)` where `n` is the number of children.
41    ///
42    /// ## Example
43    ///
44    /// ```
45    /// use kas::collection;
46    /// # use kas_widgets::{CheckBox, Flow};
47    ///
48    /// let list = Flow::right(collection![
49    ///     "A checkbox",
50    ///     CheckBox::new(|_, state: &bool| *state),
51    /// ]);
52    /// ```
53    #[autoimpl(Default where C: Default, D: Default)]
54    #[derive_widget]
55    pub struct Flow<C: Collection, D: Directional> {
56        #[widget]
57        list: List<C, D>,
58        layout: FlowStorage,
59        secondary_is_reversed: bool,
60        min_cols: i32,
61        ideal_cols: i32,
62    }
63
64    impl Layout for Self {
65        fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
66            let mut solver = FlowSolver::new(
67                axis,
68                self.list.direction.as_direction(),
69                self.secondary_is_reversed,
70                self.list.widgets.len(),
71                &mut self.layout,
72            );
73            solver.set_num_columns(self.min_cols, self.ideal_cols);
74            for n in 0..self.list.widgets.len() {
75                if let Some(child) = self.list.widgets.get_mut_tile(n) {
76                    solver.for_child(&mut self.layout, n, |axis| child.size_rules(cx, axis));
77                }
78            }
79            solver.finish(&mut self.layout)
80        }
81
82        fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, hints: AlignHints) {
83            self.list.core.set_rect(rect);
84            let mut setter = FlowSetter::new(
85                rect,
86                self.list.direction.as_direction(),
87                self.secondary_is_reversed,
88                self.list.widgets.len(),
89                &mut self.layout,
90            );
91
92            for n in 0..self.list.widgets.len() {
93                if let Some(child) = self.list.widgets.get_mut_tile(n) {
94                    child.set_rect(cx, setter.child_rect(&mut self.layout, n), hints);
95                }
96            }
97        }
98
99        fn draw(&self, mut draw: DrawCx) {
100            // TODO(opt): use position solver as with List widget
101            for child in self.list.widgets.iter_tile(..) {
102                child.draw(draw.re());
103            }
104        }
105    }
106
107    impl Tile for Self {
108        fn try_probe(&self, coord: Coord) -> Option<Id> {
109            if !self.rect().contains(coord) {
110                return None;
111            }
112
113            for child in self.list.widgets.iter_tile(..) {
114                if let Some(id) = child.try_probe(coord) {
115                    return Some(id);
116                }
117            }
118
119            Some(self.id())
120        }
121    }
122
123    impl Self
124    where
125        D: Default,
126    {
127        /// Construct a new instance with default-constructed direction
128        ///
129        /// This constructor is available where the direction is determined by the
130        /// type: for `D: Directional + Default`. The wrap direction is down or right.
131        ///
132        /// # Examples
133        ///
134        /// Where widgets have the same type and the length is fixed, an array
135        /// may be used:
136        /// ```
137        /// use kas_widgets::{Label, Row};
138        /// let _ = Row::new([Label::new("left"), Label::new("right")]);
139        /// ```
140        ///
141        /// To support run-time insertion/deletion, use [`Vec`]:
142        /// ```
143        /// use kas_widgets::{AdaptWidget, Button, Row};
144        ///
145        /// #[derive(Clone, Debug)]
146        /// enum Msg {
147        ///     Add,
148        ///     Remove,
149        /// }
150        ///
151        /// let _ = Row::new(vec![Button::label_msg("Add", Msg::Add)])
152        ///     .on_messages(|cx, row, data| {
153        ///         if let Some(msg) = cx.try_pop() {
154        ///             match msg {
155        ///                 Msg::Add => {
156        ///                     let button = if row.len() % 2 == 0 {
157        ///                         Button::label_msg("Add", Msg::Add)
158        ///                     } else {
159        ///                         Button::label_msg("Remove", Msg::Remove)
160        ///                     };
161        ///                     row.push(cx, data, button);
162        ///                 }
163        ///                 Msg::Remove => {
164        ///                     let _ = row.pop(cx);
165        ///                 }
166        ///             }
167        ///         }
168        ///     });
169        /// ```
170        #[inline]
171        pub fn new(widgets: C) -> Self {
172            Self::new_dir(widgets, D::default())
173        }
174    }
175
176    impl<C: Collection> Flow<C, kas::dir::Left> {
177        /// Construct a new instance with fixed direction
178        ///
179        /// Lines flow from right-to-left, wrapping down.
180        #[inline]
181        pub fn left(widgets: C) -> Self {
182            Self::new(widgets)
183        }
184    }
185    impl<C: Collection> Flow<C, kas::dir::Right> {
186        /// Construct a new instance with fixed direction
187        ///
188        /// Lines flow from left-to-right, wrapping down.
189        #[inline]
190        pub fn right(widgets: C) -> Self {
191            Self::new(widgets)
192        }
193    }
194
195    impl Self {
196        /// Construct a new instance with explicit direction
197        #[inline]
198        pub fn new_dir(widgets: C, direction: D) -> Self {
199            assert!(
200                direction.is_horizontal(),
201                "column flow is not (yet) supported"
202            );
203            Flow {
204                list: List::new_dir(widgets, direction),
205                layout: Default::default(),
206                secondary_is_reversed: false,
207                min_cols: 1,
208                ideal_cols: 3,
209            }
210        }
211
212        /// Set the (minimum, ideal) numbers of columns
213        ///
214        /// This affects the final [`SizeRules`] for the horizontal axis.
215        ///
216        /// By default, the values `1, 3` are used.
217        #[inline]
218        pub fn set_num_columns(&mut self, min: i32, ideal: i32) {
219            self.min_cols = min;
220            self.ideal_cols = ideal;
221        }
222
223        /// Set the (minimum, ideal) numbers of columns (inline)
224        ///
225        /// This affects the final [`SizeRules`] for the horizontal axis.
226        ///
227        /// By default, the values `1, 3` are used.
228        #[inline]
229        pub fn with_num_columns(mut self, min: i32, ideal: i32) -> Self {
230            self.set_num_columns(min, ideal);
231            self
232        }
233
234        /// True if there are no child widgets
235        pub fn is_empty(&self) -> bool {
236            self.list.is_empty()
237        }
238
239        /// Returns the number of child widgets
240        pub fn len(&self) -> usize {
241            self.list.len()
242        }
243    }
244
245    impl<W: Widget, D: Directional> Flow<Vec<W>, D> {
246        /// Returns a reference to the child, if any
247        pub fn get(&self, index: usize) -> Option<&W> {
248            self.list.get(index)
249        }
250
251        /// Returns a mutable reference to the child, if any
252        pub fn get_mut(&mut self, index: usize) -> Option<&mut W> {
253            self.list.get_mut(index)
254        }
255
256        /// Remove all child widgets
257        pub fn clear(&mut self) {
258            self.list.clear();
259        }
260
261        /// Append a child widget
262        ///
263        /// The new child is configured immediately. Triggers a resize.
264        ///
265        /// Returns the new element's index.
266        pub fn push(&mut self, cx: &mut ConfigCx, data: &W::Data, widget: W) -> usize {
267            self.list.push(cx, data, widget)
268        }
269
270        /// Remove the last child widget (if any) and return
271        ///
272        /// Triggers a resize.
273        pub fn pop(&mut self, cx: &mut ConfigCx) -> Option<W> {
274            self.list.pop(cx)
275        }
276
277        /// Inserts a child widget position `index`
278        ///
279        /// Panics if `index > len`.
280        ///
281        /// The new child is configured immediately. Triggers a resize.
282        pub fn insert(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, widget: W) {
283            self.list.insert(cx, data, index, widget);
284        }
285
286        /// Removes the child widget at position `index`
287        ///
288        /// Panics if `index` is out of bounds.
289        ///
290        /// Triggers a resize.
291        pub fn remove(&mut self, cx: &mut ConfigCx, index: usize) -> W {
292            self.list.remove(cx, index)
293        }
294
295        /// Removes all children at positions ≥ `len`
296        ///
297        /// Does nothing if `self.len() < len`.
298        ///
299        /// Triggers a resize.
300        pub fn truncate(&mut self, cx: &mut ConfigCx, len: usize) {
301            self.list.truncate(cx, len);
302        }
303
304        /// Replace the child at `index`
305        ///
306        /// Panics if `index` is out of bounds.
307        ///
308        /// The new child is configured immediately. Triggers a resize.
309        pub fn replace(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, widget: W) -> W {
310            self.list.replace(cx, data, index, widget)
311        }
312
313        /// Append child widgets from an iterator
314        ///
315        /// New children are configured immediately. Triggers a resize.
316        pub fn extend<T>(&mut self, cx: &mut ConfigCx, data: &W::Data, iter: T)
317        where
318            T: IntoIterator<Item = W>,
319        {
320            self.list.extend(cx, data, iter);
321        }
322
323        /// Resize, using the given closure to construct new widgets
324        ///
325        /// New children are configured immediately. Triggers a resize.
326        pub fn resize_with<F>(&mut self, cx: &mut ConfigCx, data: &W::Data, len: usize, f: F)
327        where
328            F: Fn(usize) -> W,
329        {
330            self.list.resize_with(cx, data, len, f);
331        }
332
333        /// Iterate over childern
334        pub fn iter(&self) -> impl Iterator<Item = &W> {
335            self.list.iter()
336        }
337
338        /// Mutably iterate over childern
339        pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut W> {
340            self.list.iter_mut()
341        }
342    }
343
344    impl<W: Widget, D: Directional> Index<usize> for Flow<Vec<W>, D> {
345        type Output = W;
346
347        fn index(&self, index: usize) -> &Self::Output {
348            self.list.index(index)
349        }
350    }
351
352    impl<W: Widget, D: Directional> IndexMut<usize> for Flow<Vec<W>, D> {
353        fn index_mut(&mut self, index: usize) -> &mut Self::Output {
354            self.list.index_mut(index)
355        }
356    }
357}