Skip to main content

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