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}