use super::{Widget, element::Element};
use alloc::vec::Vec;
use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
use zest_core::{
Constraints, Horizontal, Length, RenderError, Renderer, TouchPhase, UiAction, Vertical,
WidgetId,
};
use zest_theme::Theme;
struct Layer<'a, C: PixelColor, M: Clone> {
child: Element<'a, C, M>,
align_x: Horizontal,
align_y: Vertical,
}
pub struct Stack<'a, C: PixelColor, M: Clone> {
rect: Rectangle,
layers: Vec<Layer<'a, C, M>>,
width: Length,
height: Length,
}
impl<'a, C: PixelColor + 'a, M: Clone + 'a> Stack<'a, C, M> {
pub fn new() -> Self {
Self {
rect: Rectangle::zero(),
layers: Vec::new(),
width: Length::Fill,
height: Length::Fill,
}
}
#[must_use]
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
#[must_use]
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
#[must_use]
pub fn push<W>(self, child: W) -> Self
where
W: Widget<C, M> + 'a,
{
self.push_aligned(child, Horizontal::Center, Vertical::Center)
}
#[must_use]
pub fn push_aligned<W>(mut self, child: W, align_x: Horizontal, align_y: Vertical) -> Self
where
W: Widget<C, M> + 'a,
{
self.layers.push(Layer {
child: Element::new(child),
align_x,
align_y,
});
self
}
fn relayout(&mut self) {
let outer = Constraints::loose(self.rect.size);
let origin = self.rect.top_left;
let avail = self.rect.size;
for layer in &mut self.layers {
let desired = layer.child.measure(outer);
let cell = align_rect(origin, avail, desired, layer.align_x, layer.align_y);
layer.child.arrange(cell);
}
}
}
fn align_rect(
origin: Point,
avail: Size,
desired: Size,
align_x: Horizontal,
align_y: Vertical,
) -> Rectangle {
let w = desired.width.min(avail.width);
let h = desired.height.min(avail.height);
let free_x = avail.width.saturating_sub(w) as i32;
let free_y = avail.height.saturating_sub(h) as i32;
let dx = match align_x {
Horizontal::Left => 0,
Horizontal::Center => free_x / 2,
Horizontal::Right => free_x,
};
let dy = match align_y {
Vertical::Top => 0,
Vertical::Center => free_y / 2,
Vertical::Bottom => free_y,
};
Rectangle::new(origin + Point::new(dx, dy), Size::new(w, h))
}
impl<'a, C: PixelColor + 'a, M: Clone + 'a> Default for Stack<'a, C, M> {
fn default() -> Self {
Self::new()
}
}
impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Stack<'a, C, M> {
fn measure(&mut self, constraints: Constraints) -> Size {
let mut intrinsic = Size::zero();
let loose = Constraints::loose(constraints.max);
for layer in &mut self.layers {
let s = layer.child.measure(loose);
intrinsic = Size::new(intrinsic.width.max(s.width), intrinsic.height.max(s.height));
}
let w = self.width.resolve(intrinsic.width, constraints.max.width);
let h = self
.height
.resolve(intrinsic.height, constraints.max.height);
constraints.clamp(Size::new(w, h))
}
fn preferred_size(&self) -> (Length, Length) {
(self.width, self.height)
}
fn arrange(&mut self, rect: Rectangle) {
self.rect = rect;
self.relayout();
}
fn rect(&self) -> Rectangle {
self.rect
}
fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
for layer in self.layers.iter_mut().rev() {
if let Some(msg) = layer.child.handle_touch(point, phase) {
return Some(msg);
}
}
None
}
fn mark_pressed(&mut self, point: Point) {
for layer in &mut self.layers {
layer.child.mark_pressed(point);
}
}
fn collect_focusable(&self, out: &mut Vec<WidgetId>) {
for layer in &self.layers {
layer.child.collect_focusable(out);
}
}
fn sync_focus(&mut self, focused: Option<WidgetId>) {
for layer in &mut self.layers {
layer.child.sync_focus(focused);
}
}
fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
for layer in self.layers.iter_mut().rev() {
if let Some(msg) = layer.child.route_action(target, action) {
return Some(msg);
}
}
None
}
fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
for layer in self.layers.iter().rev() {
if let Some(next) = layer.child.navigate_focus(target, action) {
return Some(next);
}
}
None
}
fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
for layer in self.layers.iter().rev() {
if let Some(rect) = layer.child.focus_rect(target) {
return Some(rect);
}
}
None
}
fn focus_at(&self, point: Point) -> Option<WidgetId> {
for layer in self.layers.iter().rev() {
if let Some(id) = layer.child.focus_at(point) {
return Some(id);
}
}
None
}
fn draw<'t>(
&self,
renderer: &mut dyn Renderer<C>,
theme: &Theme<'t, C>,
) -> Result<(), RenderError> {
for layer in &self.layers {
layer.child.draw(renderer, theme)?;
}
Ok(())
}
}