Skip to main content

zest_widget/widget/
stack.rs

1//! Overlay container (a.k.a. ZStack / Layer). Holds children as
2//! `Vec<Element>` and stacks them on the same region:
3//!
4//! * **Draw order** is insertion order — the first child is painted at the
5//!   bottom, the last child on top.
6//! * **Touch routing** is top-first — children are polled in reverse
7//!   insertion order, so an overlay (modal, dropdown, menu) placed last
8//!   intercepts input before the widgets beneath it.
9//! * **Positioning** is per-child alignment: each child is measured against
10//!   the stack's region and placed at its requested
11//!   [`Horizontal`] / [`Vertical`] alignment (default center). A child that
12//!   asks to fill simply covers the whole stack.
13//!
14//! It backs the overlay widgets (Dropdown, MessageBox, Menu) and is kept
15//! deliberately general.
16
17use super::{Widget, element::Element};
18use alloc::vec::Vec;
19use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
20use zest_core::{
21    Constraints, Horizontal, Length, RenderError, Renderer, TouchPhase, UiAction, Vertical,
22    WidgetId,
23};
24use zest_theme::Theme;
25
26/// A single layer in a [`Stack`]: a child plus the alignment used to
27/// position it within the stack's region.
28struct Layer<'a, C: PixelColor, M: Clone> {
29    child: Element<'a, C, M>,
30    align_x: Horizontal,
31    align_y: Vertical,
32}
33
34/// Overlay container. Children share one region; later children draw on
35/// top of and receive touches before earlier ones.
36pub struct Stack<'a, C: PixelColor, M: Clone> {
37    rect: Rectangle,
38    layers: Vec<Layer<'a, C, M>>,
39    width: Length,
40    height: Length,
41}
42
43impl<'a, C: PixelColor + 'a, M: Clone + 'a> Stack<'a, C, M> {
44    /// Create a new empty stack. Position and size are assigned by the
45    /// parent via `arrange`; use `.width(...)` / `.height(...)` to
46    /// constrain the slot.
47    pub fn new() -> Self {
48        Self {
49            rect: Rectangle::zero(),
50            layers: Vec::new(),
51            width: Length::Fill,
52            height: Length::Fill,
53        }
54    }
55
56    /// Width sizing intent.
57    #[must_use]
58    pub fn width(mut self, width: impl Into<Length>) -> Self {
59        self.width = width.into();
60        self
61    }
62
63    /// Height sizing intent.
64    #[must_use]
65    pub fn height(mut self, height: impl Into<Length>) -> Self {
66        self.height = height.into();
67        self
68    }
69
70    /// Push a child centered within the stack. Drawn on top of
71    /// (and polled for touch before) every child pushed earlier.
72    #[must_use]
73    pub fn push<W>(self, child: W) -> Self
74    where
75        W: Widget<C, M> + 'a,
76    {
77        self.push_aligned(child, Horizontal::Center, Vertical::Center)
78    }
79
80    /// Push a child positioned at an explicit alignment within
81    /// the stack's region.
82    #[must_use]
83    pub fn push_aligned<W>(mut self, child: W, align_x: Horizontal, align_y: Vertical) -> Self
84    where
85        W: Widget<C, M> + 'a,
86    {
87        self.layers.push(Layer {
88            child: Element::new(child),
89            align_x,
90            align_y,
91        });
92        self
93    }
94
95    fn relayout(&mut self) {
96        let outer = Constraints::loose(self.rect.size);
97        let origin = self.rect.top_left;
98        let avail = self.rect.size;
99        for layer in &mut self.layers {
100            let desired = layer.child.measure(outer);
101            let cell = align_rect(origin, avail, desired, layer.align_x, layer.align_y);
102            layer.child.arrange(cell);
103        }
104    }
105}
106
107/// Place a `desired`-sized rect within `[origin, origin + avail)` according
108/// to the given alignments, clamping the size to the available region.
109fn align_rect(
110    origin: Point,
111    avail: Size,
112    desired: Size,
113    align_x: Horizontal,
114    align_y: Vertical,
115) -> Rectangle {
116    let w = desired.width.min(avail.width);
117    let h = desired.height.min(avail.height);
118    let free_x = avail.width.saturating_sub(w) as i32;
119    let free_y = avail.height.saturating_sub(h) as i32;
120    let dx = match align_x {
121        Horizontal::Left => 0,
122        Horizontal::Center => free_x / 2,
123        Horizontal::Right => free_x,
124    };
125    let dy = match align_y {
126        Vertical::Top => 0,
127        Vertical::Center => free_y / 2,
128        Vertical::Bottom => free_y,
129    };
130    Rectangle::new(origin + Point::new(dx, dy), Size::new(w, h))
131}
132
133impl<'a, C: PixelColor + 'a, M: Clone + 'a> Default for Stack<'a, C, M> {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Stack<'a, C, M> {
140    fn measure(&mut self, constraints: Constraints) -> Size {
141        // Intrinsic size is the max of all children's desired sizes.
142        let mut intrinsic = Size::zero();
143        let loose = Constraints::loose(constraints.max);
144        for layer in &mut self.layers {
145            let s = layer.child.measure(loose);
146            intrinsic = Size::new(intrinsic.width.max(s.width), intrinsic.height.max(s.height));
147        }
148        let w = self.width.resolve(intrinsic.width, constraints.max.width);
149        let h = self
150            .height
151            .resolve(intrinsic.height, constraints.max.height);
152        constraints.clamp(Size::new(w, h))
153    }
154
155    fn preferred_size(&self) -> (Length, Length) {
156        (self.width, self.height)
157    }
158
159    fn arrange(&mut self, rect: Rectangle) {
160        self.rect = rect;
161        self.relayout();
162    }
163
164    fn rect(&self) -> Rectangle {
165        self.rect
166    }
167
168    fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
169        // Top-first: the last-pushed (topmost) layer gets first refusal.
170        for layer in self.layers.iter_mut().rev() {
171            if let Some(msg) = layer.child.handle_touch(point, phase) {
172                return Some(msg);
173            }
174        }
175        None
176    }
177
178    fn mark_pressed(&mut self, point: Point) {
179        for layer in &mut self.layers {
180            layer.child.mark_pressed(point);
181        }
182    }
183
184    fn collect_focusable(&self, out: &mut Vec<WidgetId>) {
185        for layer in &self.layers {
186            layer.child.collect_focusable(out);
187        }
188    }
189
190    fn sync_focus(&mut self, focused: Option<WidgetId>) {
191        for layer in &mut self.layers {
192            layer.child.sync_focus(focused);
193        }
194    }
195
196    fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
197        for layer in self.layers.iter_mut().rev() {
198            if let Some(msg) = layer.child.route_action(target, action) {
199                return Some(msg);
200            }
201        }
202        None
203    }
204
205    fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
206        for layer in self.layers.iter().rev() {
207            if let Some(next) = layer.child.navigate_focus(target, action) {
208                return Some(next);
209            }
210        }
211        None
212    }
213
214    fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
215        for layer in self.layers.iter().rev() {
216            if let Some(rect) = layer.child.focus_rect(target) {
217                return Some(rect);
218            }
219        }
220        None
221    }
222
223    fn focus_at(&self, point: Point) -> Option<WidgetId> {
224        for layer in self.layers.iter().rev() {
225            if let Some(id) = layer.child.focus_at(point) {
226                return Some(id);
227            }
228        }
229        None
230    }
231
232    fn draw<'t>(
233        &self,
234        renderer: &mut dyn Renderer<C>,
235        theme: &Theme<'t, C>,
236    ) -> Result<(), RenderError> {
237        // Bottom-to-top: insertion order, last child on top.
238        for layer in &self.layers {
239            layer.child.draw(renderer, theme)?;
240        }
241        Ok(())
242    }
243}