kas_widgets/
stack.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 stack
7
8use kas::layout::solve_size_rules;
9use kas::prelude::*;
10use std::collections::hash_map::{Entry, HashMap};
11use std::fmt::Debug;
12use std::ops::{Index, IndexMut};
13
14/// A stack of boxed widgets
15///
16/// This is a parametrisation of [`Stack`].
17pub type BoxStack<Data> = Stack<Box<dyn Widget<Data = Data>>>;
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20enum State {
21    #[default]
22    None,
23    Configured,
24    Sized,
25}
26impl State {
27    fn is_configured(self) -> bool {
28        self != State::None
29    }
30}
31
32impl_scope! {
33    /// A stack of widgets
34    ///
35    /// A stack consists a set of child widgets, "pages", all of equal size.
36    /// Only a single page is visible at a time. The page is "turned" by calling
37    /// [`Self::set_active`].
38    ///
39    /// This may only be parametrised with a single widget type, thus usually
40    /// it will be necessary to box children (this is what [`BoxStack`] is).
41    ///
42    /// By default, all pages are configured and sized. To avoid configuring
43    /// hidden pages (thus preventing these pages from affecting size)
44    /// call [`Self::set_size_limit`] or [`Self::with_size_limit`].
45    #[autoimpl(Default)]
46    #[derive(Clone, Debug)]
47    #[widget]
48    pub struct Stack<W: Widget> {
49        core: widget_core!(),
50        align_hints: AlignHints,
51        widgets: Vec<(W, State)>,
52        active: usize,
53        size_limit: usize,
54        next: usize,
55        id_map: HashMap<usize, usize>, // map key of Id to index
56    }
57
58    impl Widget for Self {
59        type Data = W::Data;
60
61        fn for_child_node(
62            &mut self,
63            data: &W::Data,
64            index: usize,
65            closure: Box<dyn FnOnce(Node<'_>) + '_>,
66        ) {
67            if let Some((w, _)) = self.widgets.get_mut(index) {
68                closure(w.as_node(data));
69            }
70        }
71    }
72
73    impl Layout for Self {
74        #[inline]
75        fn num_children(&self) -> usize {
76            self.widgets.len()
77        }
78        fn get_child(&self, index: usize) -> Option<&dyn Layout> {
79            self.widgets.get(index).map(|(w, _)| w.as_layout())
80        }
81
82        fn find_child_index(&self, id: &Id) -> Option<usize> {
83            id.next_key_after(self.id_ref())
84                .and_then(|k| self.id_map.get(&k).cloned())
85        }
86
87        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
88            let mut rules = SizeRules::EMPTY;
89            let mut index = 0;
90            let end = self.widgets.len().min(self.size_limit);
91            loop {
92                if index == end {
93                    if self.active >= end {
94                        index = self.active;
95                    } else {
96                        break rules;
97                    }
98                } else if index > end {
99                    break rules;
100                }
101
102                if let Some(entry) = self.widgets.get_mut(index) {
103                    if entry.1.is_configured() {
104                        rules = rules.max(entry.0.size_rules(sizer.re(), axis));
105                        entry.1 = State::Sized;
106                    }
107                }
108
109                index += 1;
110            }
111        }
112
113        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
114            self.core.rect = rect;
115            self.align_hints = hints;
116            if let Some(entry) = self.widgets.get_mut(self.active) {
117                debug_assert_eq!(entry.1, State::Sized);
118                entry.0.set_rect(cx, rect, hints);
119            }
120        }
121
122        fn nav_next(&self, _: bool, from: Option<usize>) -> Option<usize> {
123            match from {
124                None => Some(self.active),
125                Some(active) if active != self.active => Some(self.active),
126                _ => None,
127            }
128        }
129
130        fn find_id(&mut self, coord: Coord) -> Option<Id> {
131            if let Some(entry) = self.widgets.get_mut(self.active) {
132                debug_assert_eq!(entry.1, State::Sized);
133                return entry.0.find_id(coord);
134            }
135            None
136        }
137
138        fn draw(&mut self, mut draw: DrawCx) {
139            if let Some(entry) = self.widgets.get_mut(self.active) {
140                debug_assert_eq!(entry.1, State::Sized);
141                draw.recurse(&mut entry.0);
142            }
143        }
144    }
145
146    impl Events for Self {
147        fn make_child_id(&mut self, index: usize) -> Id {
148            if let Some((child, state)) = self.widgets.get(index) {
149                if state.is_configured() {
150                    debug_assert!(child.id_ref().is_valid());
151                    if let Some(key) = child.id_ref().next_key_after(self.id_ref()) {
152                        debug_assert_eq!(self.id_map.get(&key), Some(&index));
153                    } else {
154                        debug_assert!(false);
155                    }
156                    return child.id();
157                }
158
159                // Use the widget's existing identifier, if any
160                if child.id_ref().is_valid() {
161                    if let Some(key) = child.id_ref().next_key_after(self.id_ref()) {
162                        self.id_map.insert(key, index);
163                        return child.id();
164                    }
165                }
166            }
167
168            loop {
169                let key = self.next;
170                self.next += 1;
171                if let Entry::Vacant(entry) = self.id_map.entry(key) {
172                    entry.insert(index);
173                    return self.id_ref().make_child(key);
174                }
175            }
176        }
177
178        fn configure(&mut self, _: &mut ConfigCx) {
179            self.id_map.clear();
180        }
181
182        fn configure_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
183            let mut index = 0;
184            let end = self.widgets.len().min(self.size_limit);
185            loop {
186                if index == end {
187                    if self.active >= end {
188                        index = self.active;
189                    } else {
190                        break;
191                    }
192                } else if index > end {
193                    break;
194                }
195
196                let id = self.make_child_id(index);
197                if let Some(entry) = self.widgets.get_mut(index) {
198                    cx.configure(entry.0.as_node(data), id);
199                    if entry.1 == State::None {
200                        entry.1 = State::Configured;
201                    }
202                }
203
204                index += 1;
205            }
206        }
207
208        fn update_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
209            if let Some((w, _)) = self.widgets.get_mut(self.active) {
210                cx.update(w.as_node(data));
211            }
212        }
213    }
214
215    impl Index<usize> for Self {
216        type Output = W;
217
218        fn index(&self, index: usize) -> &Self::Output {
219            &self.widgets[index].0
220        }
221    }
222
223    impl IndexMut<usize> for Self {
224        fn index_mut(&mut self, index: usize) -> &mut Self::Output {
225            &mut self.widgets[index].0
226        }
227    }
228}
229
230impl<W: Widget> Stack<W> {
231    /// Construct a new, empty instance
232    ///
233    /// See also [`Stack::from`].
234    pub fn new() -> Self {
235        Stack::default()
236    }
237
238    /// Limit the number of pages considered and sized
239    ///
240    /// By default, this is `usize::MAX`: all pages are configured and affect
241    /// the stack's size requirements.
242    ///
243    /// Set this to 0 to avoid configuring all hidden pages.
244    /// Set this to `n` to configure the active page *and* the first `n` pages.
245    pub fn set_size_limit(&mut self, limit: usize) {
246        self.size_limit = limit;
247    }
248
249    /// Limit the number of pages configured and sized (inline)
250    ///
251    /// By default, this is `usize::MAX`: all pages are configured and affect
252    /// the stack's size requirements.
253    ///
254    /// Set this to 0 to avoid configuring all hidden pages.
255    /// Set this to `n` to configure the active page *and* the first `n` pages.
256    pub fn with_size_limit(mut self, limit: usize) -> Self {
257        self.size_limit = limit;
258        self
259    }
260
261    /// Get the index of the active widget
262    #[inline]
263    pub fn active(&self) -> usize {
264        self.active
265    }
266
267    /// Set the active widget (inline)
268    ///
269    /// Unlike [`Self::set_active`], this does not update anything; it is
270    /// assumed that sizing happens afterwards.
271    #[inline]
272    pub fn with_active(mut self, active: usize) -> Self {
273        self.active = active;
274        self
275    }
276
277    /// Set the active page
278    pub fn set_active(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize) {
279        let old_index = self.active;
280        if old_index == index {
281            return;
282        }
283        self.active = index;
284
285        let id = self.make_child_id(index);
286        if let Some(entry) = self.widgets.get_mut(index) {
287            let node = entry.0.as_node(data);
288
289            if entry.1 == State::None {
290                cx.configure(node, id);
291                entry.1 = State::Configured;
292            } else {
293                cx.update(node);
294            }
295
296            if entry.1 == State::Configured {
297                cx.resize(self);
298            } else {
299                debug_assert_eq!(entry.1, State::Sized);
300                entry.0.set_rect(cx, self.core.rect, self.align_hints);
301                cx.region_moved();
302            }
303        } else {
304            if old_index < self.widgets.len() {
305                cx.region_moved();
306            }
307        }
308    }
309
310    /// Get a direct reference to the active child widget, if any
311    pub fn get_active(&self) -> Option<&W> {
312        if self.active < self.widgets.len() {
313            Some(&self.widgets[self.active].0)
314        } else {
315            None
316        }
317    }
318
319    /// True if there are no pages
320    pub fn is_empty(&self) -> bool {
321        self.widgets.is_empty()
322    }
323
324    /// Returns the number of pages
325    pub fn len(&self) -> usize {
326        self.widgets.len()
327    }
328
329    /// Remove all pages
330    ///
331    /// This does not change the activen page index.
332    pub fn clear(&mut self) {
333        self.widgets.clear();
334    }
335
336    /// Returns a reference to the page, if any
337    pub fn get(&self, index: usize) -> Option<&W> {
338        self.widgets.get(index).map(|e| &e.0)
339    }
340
341    /// Returns a mutable reference to the page, if any
342    pub fn get_mut(&mut self, index: usize) -> Option<&mut W> {
343        self.widgets.get_mut(index).map(|e| &mut e.0)
344    }
345
346    /// Configure and size the page at index
347    fn configure_and_size(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize) {
348        let id = self.make_child_id(index);
349        if let Some(entry) = self.widgets.get_mut(index) {
350            cx.configure(entry.0.as_node(data), id);
351            let Size(w, h) = self.core.rect.size;
352            solve_size_rules(&mut entry.0, cx.size_cx(), Some(w), Some(h));
353            entry.1 = State::Sized;
354        }
355    }
356
357    /// Append a page
358    ///
359    /// The new page is not made active (the active index may be changed to
360    /// avoid this). Consider calling [`Self::set_active`].
361    ///
362    /// Returns the new page's index.
363    pub fn push(&mut self, cx: &mut ConfigCx, data: &W::Data, widget: W) -> usize {
364        let index = self.widgets.len();
365        if index == self.active {
366            self.active = usize::MAX;
367        }
368        self.widgets.push((widget, State::None));
369
370        if index < self.size_limit {
371            self.configure_and_size(cx, data, index);
372        }
373        index
374    }
375
376    /// Remove the last child widget (if any) and return
377    ///
378    /// If this page was active then no page will be left active.
379    /// Consider also calling [`Self::set_active`].
380    pub fn pop(&mut self, cx: &mut EventState) -> Option<W> {
381        let result = self.widgets.pop().map(|(w, _)| w);
382        if let Some(w) = result.as_ref() {
383            if self.active > 0 && self.active == self.widgets.len() {
384                cx.region_moved();
385            }
386
387            if w.id_ref().is_valid() {
388                if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
389                    self.id_map.remove(&key);
390                }
391            }
392        }
393        result
394    }
395
396    /// Inserts a child widget position `index`
397    ///
398    /// Panics if `index > len`.
399    ///
400    /// The active page does not change (the index of the active page may change instead).
401    pub fn insert(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, widget: W) {
402        if self.active >= index {
403            self.active = self.active.saturating_add(1);
404        }
405
406        self.widgets.insert(index, (widget, State::None));
407
408        for v in self.id_map.values_mut() {
409            if *v >= index {
410                *v += 1;
411            }
412        }
413
414        if index < self.size_limit {
415            self.configure_and_size(cx, data, index);
416        }
417    }
418
419    /// Removes the child widget at position `index`
420    ///
421    /// Panics if `index` is out of bounds.
422    ///
423    /// If this page was active then no page will be left active.
424    /// Consider also calling [`Self::set_active`].
425    pub fn remove(&mut self, cx: &mut EventState, index: usize) -> W {
426        let (w, _) = self.widgets.remove(index);
427        if w.id_ref().is_valid() {
428            if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
429                self.id_map.remove(&key);
430            }
431        }
432
433        if self.active == index {
434            self.active = usize::MAX;
435            cx.region_moved();
436        }
437
438        for v in self.id_map.values_mut() {
439            if *v > index {
440                *v -= 1;
441            }
442        }
443        w
444    }
445
446    /// Replace the child at `index`
447    ///
448    /// Panics if `index` is out of bounds.
449    ///
450    /// If the new child replaces the active page then [`Action::RESIZE`] is triggered.
451    pub fn replace(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, mut widget: W) -> W {
452        let entry = &mut self.widgets[index];
453        std::mem::swap(&mut widget, &mut entry.0);
454        entry.1 = State::None;
455
456        if widget.id_ref().is_valid() {
457            if let Some(key) = widget.id_ref().next_key_after(self.id_ref()) {
458                self.id_map.remove(&key);
459            }
460        }
461
462        if index < self.size_limit || index == self.active {
463            self.configure_and_size(cx, data, index);
464        }
465
466        if index == self.active {
467            cx.resize(self);
468        }
469
470        widget
471    }
472
473    /// Append child widgets from an iterator
474    ///
475    /// The new pages are not made active (the active index may be changed to
476    /// avoid this). Consider calling [`Self::set_active`].
477    pub fn extend<T: IntoIterator<Item = W>>(
478        &mut self,
479        cx: &mut ConfigCx,
480        data: &W::Data,
481        iter: T,
482    ) {
483        let old_len = self.widgets.len();
484        let iter = iter.into_iter();
485        if let Some(ub) = iter.size_hint().1 {
486            self.widgets.reserve(ub);
487        }
488        for w in iter {
489            let index = self.widgets.len();
490            self.widgets.push((w, State::None));
491            if index < self.size_limit {
492                self.configure_and_size(cx, data, index);
493            }
494        }
495
496        if (old_len..self.widgets.len()).contains(&self.active) {
497            self.active = usize::MAX;
498        }
499    }
500
501    /// Resize, using the given closure to construct new widgets
502    ///
503    /// The new pages are not made active (the active index may be changed to
504    /// avoid this). Consider calling [`Self::set_active`].
505    pub fn resize_with<F: Fn(usize) -> W>(
506        &mut self,
507        cx: &mut ConfigCx,
508        data: &W::Data,
509        len: usize,
510        f: F,
511    ) {
512        let old_len = self.widgets.len();
513
514        if len < old_len {
515            loop {
516                let (w, _) = self.widgets.pop().unwrap();
517                if w.id_ref().is_valid() {
518                    if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
519                        self.id_map.remove(&key);
520                    }
521                }
522                if len == self.widgets.len() {
523                    return;
524                }
525            }
526        }
527
528        if len > old_len {
529            self.widgets.reserve(len - old_len);
530            for index in old_len..len {
531                self.widgets.push((f(index), State::None));
532                if index < self.size_limit {
533                    self.configure_and_size(cx, data, index);
534                }
535            }
536
537            if (old_len..len).contains(&self.active) {
538                self.active = usize::MAX;
539            }
540        }
541    }
542}
543
544impl<W: Widget, I> From<I> for Stack<W>
545where
546    I: IntoIterator<Item = W>,
547{
548    #[inline]
549    fn from(iter: I) -> Self {
550        Self {
551            widgets: iter.into_iter().map(|w| (w, State::None)).collect(),
552            ..Default::default()
553        }
554    }
555}