kas_widgets/list.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 run-time adjustable contents
7
8use kas::Collection;
9use kas::dir::{Down, Right};
10use kas::layout::{
11 DynRowStorage, RowPositionSolver, RowSetter, RowSolver, RowStorage, RulesSetter, RulesSolver,
12};
13use kas::prelude::*;
14use std::collections::hash_map::{Entry, HashMap};
15use std::ops::{Index, IndexMut};
16
17/// Make a [`Row`] widget
18///
19/// # Syntax
20///
21/// > _Collection_ :\
22/// > `row!` `[` _Items_<sup>\?</sup> `]`
23/// >
24/// > _Items_ :\
25/// > (_Item_ `,`)<sup>\*</sup> _Item_ `,`<sup>\?</sup>
26///
27/// ## Stand-alone usage
28///
29/// When used as a stand-alone macro, `row! [/* ... */]` is just syntactic sugar
30/// for `Row::new(kas::collection! [/* ... */])`.
31///
32/// In this case, _Item_ may be:
33///
34/// - A string literal (interpreted as a label widget), optionally followed by
35/// an [`align`] or [`pack`] method call
36/// - An expression yielding an object implementing `Widget<Data = _A>`
37///
38/// In case all _Item_ instances are a string literal, the data type of the
39/// `row!` widget will be `()`; otherwise the data type of the widget is `_A`
40/// where `_A` is a generic type parameter of the widget.
41///
42/// ## Usage within widget layout syntax
43///
44/// When called within [widget layout syntax], `row!` may be evaluated as a
45/// recursive macro and the result does not have a specified type, except that
46/// methods [`map_any`], [`align`] and [`pack`] are supported via emulation.
47///
48/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
49/// broadly similar to the above with a couple of exceptions:
50///
51/// - Supported layout macros do not need to be imported to the module scope
52/// - An _Item_ may be a `#[widget]` field of the widget
53///
54/// # Example
55///
56/// ```
57/// let my_widget = kas_widgets::row!["one", "two"];
58/// ```
59///
60/// [widget layout syntax]: macro@kas::layout
61/// [`map_any`]: crate::AdaptWidgetAny::map_any
62/// [`align`]: crate::AdaptWidget::align
63/// [`pack`]: crate::AdaptWidget::pack
64#[macro_export]
65macro_rules! row {
66 ( $( $ee:expr ),* ) => {
67 $crate::Row::new( ::kas::collection! [ $( $ee ),* ] )
68 };
69 ( $( $ee:expr ),+ , ) => {
70 $crate::Row::new( ::kas::collection! [ $( $ee ),+ ] )
71 };
72}
73
74/// Make a [`Column`] widget
75///
76/// # Syntax
77///
78/// > _Collection_ :\
79/// > `column!` `[` _Items_<sup>\?</sup> `]`
80/// >
81/// > _Items_ :\
82/// > (_Item_ `,`)<sup>\*</sup> _Item_ `,`<sup>\?</sup>
83///
84/// ## Stand-alone usage
85///
86/// When used as a stand-alone macro, `column! [/* ... */]` is just syntactic sugar
87/// for `Column::new(kas::collection! [/* ... */])`.
88///
89/// In this case, _Item_ may be:
90///
91/// - A string literal (interpreted as a label widget), optionally followed by
92/// an [`align`] or [`pack`] method call
93/// - An expression yielding an object implementing `Widget<Data = _A>`
94///
95/// In case all _Item_ instances are a string literal, the data type of the
96/// `column!` widget will be `()`; otherwise the data type of the widget is `_A`
97/// where `_A` is a generic type parameter of the widget.
98///
99/// ## Usage within widget layout syntax
100///
101/// When called within [widget layout syntax], `column!` may be evaluated as a
102/// recursive macro and the result does not have a specified type, except that
103/// methods [`map_any`], [`align`] and [`pack`] are supported via emulation.
104///
105/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
106/// broadly similar to the above with a couple of exceptions:
107///
108/// - Supported layout macros do not need to be imported to the module scope
109/// - An _Item_ may be a `#[widget]` field of the widget
110///
111/// # Example
112///
113/// ```
114/// let my_widget = kas_widgets::column! [
115/// "one",
116/// "two",
117/// ];
118/// ```
119///
120/// [widget layout syntax]: macro@kas::layout
121/// [`map_any`]: crate::AdaptWidgetAny::map_any
122/// [`align`]: crate::AdaptWidget::align
123/// [`pack`]: crate::AdaptWidget::pack
124#[macro_export]
125macro_rules! column {
126 ( $( $ee:expr ),* ) => {
127 $crate::Column::new( ::kas::collection! [ $( $ee ),* ] )
128 };
129 ( $( $ee:expr ),+ , ) => {
130 $crate::Column::new( ::kas::collection! [ $( $ee ),+ ] )
131 };
132}
133
134/// Make a [`List`] widget
135///
136/// # Syntax
137///
138/// > _Collection_ :\
139/// > `list!` `[` _Items_<sup>\?</sup> `]`
140/// >
141/// > _Items_ :\
142/// > (_Item_ `,`)<sup>\*</sup> _Item_ `,`<sup>\?</sup>
143///
144/// ## Stand-alone usage
145///
146/// When used as a stand-alone macro, `list! [/* ... */]` is just syntactic sugar
147/// for `List::new(kas::collection! [/* ... */])`.
148///
149/// In this case, _Item_ may be:
150///
151/// - A string literal (interpreted as a label widget), optionally followed by
152/// an [`align`] or [`pack`] method call
153/// - An expression yielding an object implementing `Widget<Data = _A>`
154///
155/// In case all _Item_ instances are a string literal, the data type of the
156/// `list!` widget will be `()`; otherwise the data type of the widget is `_A`
157/// where `_A` is a generic type parameter of the widget.
158///
159/// ## Usage within widget layout syntax
160///
161/// When called within [widget layout syntax], `list!` may be evaluated as a
162/// recursive macro and the result does not have a specified type, except that
163/// methods [`map_any`], [`align`], [`pack`] and [`with_direction`] are
164/// supported via emulation. In this case, calling [`with_direction`] is
165/// required. Note that the argument passed to [`with_direction`] is expanded
166/// at the use site, so for example `.with_direction(self.dir)` will read
167/// `self.dir` whenever layout is computed.
168///
169/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
170/// broadly similar to the above with a couple of exceptions:
171///
172/// - Supported layout macros do not need to be imported to the module scope
173/// - An _Item_ may be a `#[widget]` field of the widget
174///
175/// # Example
176///
177/// ```
178/// let my_widget = kas_widgets::list! ["one", "two"]
179/// .with_direction(kas::dir::Left);
180/// ```
181///
182/// [widget layout syntax]: macro@kas::layout
183/// [`map_any`]: crate::AdaptWidgetAny::map_any
184/// [`align`]: crate::AdaptWidget::align
185/// [`pack`]: crate::AdaptWidget::pack
186/// [`with_direction`]: List::with_direction
187#[macro_export]
188macro_rules! list {
189 ( $( $ee:expr ),* ) => {
190 $crate::List::new( ::kas::collection! [ $( $ee ),* ] )
191 };
192 ( $( $ee:expr ),+ , ) => {
193 $crate::List::new( ::kas::collection! [ $( $ee ),+ ] )
194 };
195}
196
197/// A generic row widget
198///
199/// See documentation of [`List`] type.
200pub type Row<C> = List<C, Right>;
201
202/// A generic column widget
203///
204/// See documentation of [`List`] type.
205pub type Column<C> = List<C, Down>;
206
207#[impl_self]
208mod List {
209 /// A generic row/column widget
210 ///
211 /// A linear widget over a [`Collection`] of widgets.
212 ///
213 /// When the collection uses [`Vec`], various methods to insert/remove
214 /// elements are available.
215 ///
216 /// The layout direction `D` may be compile-time fixed (e.g. [`Right`]) or
217 /// run-time mutable ([`Direction`]); in the latter case
218 /// [`set_direction`] is available.
219 ///
220 /// ## See also
221 ///
222 /// [`Row`] and [`Column`] are type-defs to `List` which fix the direction `D`.
223 ///
224 /// The macros [`row!`] and [`column!`] also create row/column
225 /// layouts, but are not fully equivalent:
226 ///
227 /// - `row!` and `column!` generate anonymous layout widgets (or objects).
228 /// These do not have a [`set_direction`] method or support adding or
229 /// removing elements.
230 /// - `row!` and `column!` generate layout objects which, when used within
231 /// a custom widget, may refer to that widget's fields.
232 ///
233 /// ## Performance
234 ///
235 /// Configuring and resizing elements is O(n) in the number of children.
236 /// Drawing and event handling is O(log n) in the number of children (assuming
237 /// only a small number are visible at any one time).
238 ///
239 /// ## Example
240 ///
241 /// ```
242 /// use kas::collection;
243 /// # use kas_widgets::{CheckBox, List};
244 ///
245 /// let list = List::right(collection![
246 /// "A checkbox",
247 /// CheckBox::new(|_, state: &bool| *state),
248 /// ]);
249 /// ```
250 ///
251 /// [`row!`]: crate::row
252 /// [`column!`]: crate::column
253 /// [`set_direction`]: List::set_direction
254 #[autoimpl(Default where C: Default, D: Default)]
255 #[widget]
256 pub struct List<C: Collection, D: Directional> {
257 core: widget_core!(),
258 layout: DynRowStorage,
259 #[collection]
260 widgets: C,
261 direction: D,
262 next: usize,
263 id_map: HashMap<usize, usize>, // map key of Id to index
264 }
265
266 impl Layout for Self {
267 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
268 let dim = (self.direction, self.widgets.len());
269 let mut solver = RowSolver::new(axis, dim, &mut self.layout);
270 for n in 0..self.widgets.len() {
271 if let Some(child) = self.widgets.get_mut_tile(n) {
272 solver.for_child(&mut self.layout, n, |axis| {
273 child.size_rules(sizer.re(), axis)
274 });
275 }
276 }
277 solver.finish(&mut self.layout)
278 }
279
280 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
281 widget_set_rect!(rect);
282 let dim = (self.direction, self.widgets.len());
283 let mut setter = RowSetter::<D, Vec<i32>, _>::new(rect, dim, &mut self.layout);
284
285 for n in 0..self.widgets.len() {
286 if let Some(child) = self.widgets.get_mut_tile(n) {
287 child.set_rect(cx, setter.child_rect(&mut self.layout, n), hints);
288 }
289 }
290 }
291
292 fn draw(&self, mut draw: DrawCx) {
293 let solver = RowPositionSolver::new(self.direction);
294 solver.for_children(&self.widgets, draw.get_clip_rect(), |w| w.draw(draw.re()));
295 }
296 }
297
298 impl Tile for Self {
299 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
300 Role::None
301 }
302
303 fn find_child_index(&self, id: &Id) -> Option<usize> {
304 id.next_key_after(self.id_ref())
305 .and_then(|k| self.id_map.get(&k).cloned())
306 }
307
308 fn probe(&self, coord: Coord) -> Id {
309 let solver = RowPositionSolver::new(self.direction);
310 solver
311 .find_child(&self.widgets, coord)
312 .and_then(|child| child.try_probe(coord))
313 .unwrap_or_else(|| self.id())
314 }
315 }
316
317 impl Events for Self {
318 type Data = C::Data;
319
320 /// Make a fresh id based on `self.next` then insert into `self.id_map`
321 fn make_child_id(&mut self, index: usize) -> Id {
322 if let Some(child) = self.widgets.get_tile(index) {
323 // Use the widget's existing identifier, if valid
324 if child.id_ref().is_valid() && self.id_ref().is_ancestor_of(child.id_ref()) {
325 if let Some(key) = child.id_ref().next_key_after(self.id_ref()) {
326 if let Entry::Vacant(entry) = self.id_map.entry(key) {
327 entry.insert(index);
328 return child.id();
329 }
330 }
331 }
332 }
333
334 loop {
335 let key = self.next;
336 self.next += 1;
337 if let Entry::Vacant(entry) = self.id_map.entry(key) {
338 entry.insert(index);
339 return self.id_ref().make_child(key);
340 }
341 }
342 }
343
344 fn configure(&mut self, _: &mut ConfigCx) {
345 // All children will be re-configured which will rebuild id_map
346 self.id_map.clear();
347 }
348 }
349
350 impl Self
351 where
352 D: Default,
353 {
354 /// Construct a new instance with default-constructed direction
355 ///
356 /// This constructor is available where the direction is determined by the
357 /// type: for `D: Directional + Default`. In other cases, use
358 /// [`Self::new_dir`].
359 ///
360 /// # Examples
361 ///
362 /// Where widgets have the same type and the length is fixed, an array
363 /// may be used:
364 /// ```
365 /// use kas_widgets::{Label, Row};
366 /// let _ = Row::new([Label::new("left"), Label::new("right")]);
367 /// ```
368 ///
369 /// To support run-time insertion/deletion, use [`Vec`]:
370 /// ```
371 /// use kas_widgets::{AdaptWidget, Button, Row};
372 ///
373 /// #[derive(Clone, Debug)]
374 /// enum Msg {
375 /// Add,
376 /// Remove,
377 /// }
378 ///
379 /// let _ = Row::new(vec![Button::label_msg("Add", Msg::Add)])
380 /// .on_messages(|cx, row, data| {
381 /// if let Some(msg) = cx.try_pop() {
382 /// match msg {
383 /// Msg::Add => {
384 /// let button = if row.len() % 2 == 0 {
385 /// Button::label_msg("Add", Msg::Add)
386 /// } else {
387 /// Button::label_msg("Remove", Msg::Remove)
388 /// };
389 /// row.push(&mut cx.config_cx(), data, button);
390 /// }
391 /// Msg::Remove => {
392 /// let _ = row.pop(&mut cx.config_cx());
393 /// }
394 /// }
395 /// }
396 /// });
397 /// ```
398 #[inline]
399 pub fn new(widgets: C) -> Self {
400 Self::new_dir(widgets, D::default())
401 }
402 }
403
404 impl<C: Collection> List<C, kas::dir::Left> {
405 /// Construct a new instance with fixed direction
406 #[inline]
407 pub fn left(widgets: C) -> Self {
408 Self::new(widgets)
409 }
410 }
411 impl<C: Collection> List<C, kas::dir::Right> {
412 /// Construct a new instance with fixed direction
413 #[inline]
414 pub fn right(widgets: C) -> Self {
415 Self::new(widgets)
416 }
417 }
418 impl<C: Collection> List<C, kas::dir::Up> {
419 /// Construct a new instance with fixed direction
420 #[inline]
421 pub fn up(widgets: C) -> Self {
422 Self::new(widgets)
423 }
424 }
425 impl<C: Collection> List<C, kas::dir::Down> {
426 /// Construct a new instance with fixed direction
427 #[inline]
428 pub fn down(widgets: C) -> Self {
429 Self::new(widgets)
430 }
431 }
432
433 impl<C: Collection, D: Directional + Eq> List<C, D> {
434 /// Set the direction of contents
435 pub fn set_direction(&mut self, cx: &mut EventState, direction: D) {
436 if direction == self.direction {
437 return;
438 }
439
440 self.direction = direction;
441 // Note: most of the time Action::SET_RECT would be enough, but margins can be different
442 cx.resize(self);
443 }
444 }
445
446 impl Self {
447 /// Construct a new instance with explicit direction
448 #[inline]
449 pub fn new_dir(widgets: C, direction: D) -> Self {
450 List {
451 core: Default::default(),
452 layout: Default::default(),
453 widgets,
454 direction,
455 next: 0,
456 id_map: Default::default(),
457 }
458 }
459
460 /// Get the direction of contents
461 pub fn direction(&self) -> Direction {
462 self.direction.as_direction()
463 }
464
465 /// Set the direction of contents (inline)
466 #[inline]
467 pub fn with_direction(mut self, direction: D) -> Self {
468 self.direction = direction;
469 self
470 }
471
472 /// Access layout storage
473 ///
474 /// The number of columns/rows is [`Self::len`].
475 #[inline]
476 pub fn layout_storage(&mut self) -> &mut (impl RowStorage + use<C, D>) {
477 &mut self.layout
478 }
479
480 /// True if there are no child widgets
481 pub fn is_empty(&self) -> bool {
482 self.widgets.is_empty()
483 }
484
485 /// Returns the number of child widgets
486 pub fn len(&self) -> usize {
487 self.widgets.len()
488 }
489 }
490
491 impl<W: Widget, D: Directional> List<Vec<W>, D> {
492 /// Returns a reference to the child, if any
493 pub fn get(&self, index: usize) -> Option<&W> {
494 self.widgets.get(index)
495 }
496
497 /// Returns a mutable reference to the child, if any
498 pub fn get_mut(&mut self, index: usize) -> Option<&mut W> {
499 self.widgets.get_mut(index)
500 }
501
502 /// Remove all child widgets
503 pub fn clear(&mut self) {
504 self.widgets.clear();
505 }
506
507 /// Append a child widget
508 ///
509 /// The new child is configured immediately. [`Action::RESIZE`] is
510 /// triggered.
511 ///
512 /// Returns the new element's index.
513 pub fn push(&mut self, cx: &mut ConfigCx, data: &W::Data, mut widget: W) -> usize {
514 let index = self.widgets.len();
515 let id = self.make_child_id(index);
516 cx.configure(widget.as_node(data), id);
517 self.widgets.push(widget);
518
519 cx.resize(self);
520 index
521 }
522
523 /// Remove the last child widget (if any) and return
524 ///
525 /// Triggers [`Action::RESIZE`].
526 pub fn pop(&mut self, cx: &mut EventState) -> Option<W> {
527 let result = self.widgets.pop();
528 if let Some(w) = result.as_ref() {
529 cx.resize(&self);
530
531 if w.id_ref().is_valid() {
532 if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
533 self.id_map.remove(&key);
534 }
535 }
536 }
537 result
538 }
539
540 /// Inserts a child widget position `index`
541 ///
542 /// Panics if `index > len`.
543 ///
544 /// The new child is configured immediately. Triggers [`Action::RESIZE`].
545 pub fn insert(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, mut widget: W) {
546 for v in self.id_map.values_mut() {
547 if *v >= index {
548 *v += 1;
549 }
550 }
551
552 let id = self.make_child_id(index);
553 cx.configure(widget.as_node(data), id);
554 self.widgets.insert(index, widget);
555 cx.resize(self);
556 }
557
558 /// Removes the child widget at position `index`
559 ///
560 /// Panics if `index` is out of bounds.
561 ///
562 /// Triggers [`Action::RESIZE`].
563 pub fn remove(&mut self, cx: &mut EventState, index: usize) -> W {
564 let w = self.widgets.remove(index);
565 if w.id_ref().is_valid() {
566 if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
567 self.id_map.remove(&key);
568 }
569 }
570
571 cx.resize(&self);
572
573 for v in self.id_map.values_mut() {
574 if *v > index {
575 *v -= 1;
576 }
577 }
578 w
579 }
580
581 /// Replace the child at `index`
582 ///
583 /// Panics if `index` is out of bounds.
584 ///
585 /// The new child is configured immediately. Triggers [`Action::RESIZE`].
586 pub fn replace(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, mut w: W) -> W {
587 let id = self.make_child_id(index);
588 cx.configure(w.as_node(data), id);
589 std::mem::swap(&mut w, &mut self.widgets[index]);
590
591 if w.id_ref().is_valid() {
592 if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
593 self.id_map.remove(&key);
594 }
595 }
596
597 cx.resize(self);
598
599 w
600 }
601
602 /// Append child widgets from an iterator
603 ///
604 /// New children are configured immediately. Triggers [`Action::RESIZE`].
605 pub fn extend<T>(&mut self, cx: &mut ConfigCx, data: &W::Data, iter: T)
606 where
607 T: IntoIterator<Item = W>,
608 {
609 let iter = iter.into_iter();
610 if let Some(ub) = iter.size_hint().1 {
611 self.widgets.reserve(ub);
612 }
613 for mut w in iter {
614 let id = self.make_child_id(self.widgets.len());
615 cx.configure(w.as_node(data), id);
616 self.widgets.push(w);
617 }
618
619 cx.resize(self);
620 }
621
622 /// Resize, using the given closure to construct new widgets
623 ///
624 /// New children are configured immediately. Triggers [`Action::RESIZE`].
625 pub fn resize_with<F>(&mut self, cx: &mut ConfigCx, data: &W::Data, len: usize, f: F)
626 where
627 F: Fn(usize) -> W,
628 {
629 let old_len = self.widgets.len();
630
631 if len < old_len {
632 cx.resize(&self);
633 loop {
634 let w = self.widgets.pop().unwrap();
635 if w.id_ref().is_valid() {
636 if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
637 self.id_map.remove(&key);
638 }
639 }
640 if len == self.widgets.len() {
641 return;
642 }
643 }
644 }
645
646 if len > old_len {
647 self.widgets.reserve(len - old_len);
648 for index in old_len..len {
649 let id = self.make_child_id(index);
650 let mut w = f(index);
651 cx.configure(w.as_node(data), id);
652 self.widgets.push(w);
653 }
654 cx.resize(self);
655 }
656 }
657
658 /// Iterate over childern
659 pub fn iter(&self) -> impl Iterator<Item = &W> {
660 ListIter {
661 list: &self.widgets,
662 }
663 }
664
665 /// Mutably iterate over childern
666 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut W> {
667 ListIterMut {
668 list: &mut self.widgets,
669 }
670 }
671 }
672
673 impl<W: Widget, D: Directional> Index<usize> for List<Vec<W>, D> {
674 type Output = W;
675
676 fn index(&self, index: usize) -> &Self::Output {
677 &self.widgets[index]
678 }
679 }
680
681 impl<W: Widget, D: Directional> IndexMut<usize> for List<Vec<W>, D> {
682 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
683 &mut self.widgets[index]
684 }
685 }
686}
687
688struct ListIter<'a, W: Widget> {
689 list: &'a [W],
690}
691impl<'a, W: Widget> Iterator for ListIter<'a, W> {
692 type Item = &'a W;
693 fn next(&mut self) -> Option<Self::Item> {
694 if let Some((first, rest)) = self.list.split_first() {
695 self.list = rest;
696 Some(first)
697 } else {
698 None
699 }
700 }
701 fn size_hint(&self) -> (usize, Option<usize>) {
702 let len = self.len();
703 (len, Some(len))
704 }
705}
706impl<'a, W: Widget> ExactSizeIterator for ListIter<'a, W> {
707 fn len(&self) -> usize {
708 self.list.len()
709 }
710}
711
712struct ListIterMut<'a, W: Widget> {
713 list: &'a mut [W],
714}
715impl<'a, W: Widget> Iterator for ListIterMut<'a, W> {
716 type Item = &'a mut W;
717 fn next(&mut self) -> Option<Self::Item> {
718 let list = std::mem::take(&mut self.list);
719 if let Some((first, rest)) = list.split_first_mut() {
720 self.list = rest;
721 Some(first)
722 } else {
723 None
724 }
725 }
726 fn size_hint(&self) -> (usize, Option<usize>) {
727 let len = self.len();
728 (len, Some(len))
729 }
730}
731impl<'a, W: Widget> ExactSizeIterator for ListIterMut<'a, W> {
732 fn len(&self) -> usize {
733 self.list.len()
734 }
735}