From 0c74d2645649a88799a894ed684a728d135043fa Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 25 Apr 2024 01:39:34 +0200
Subject: Implement `Stack` widget

It can be used to stack elements on top of each other!
---
 core/src/layout.rs    |   2 +-
 widget/src/helpers.rs |  27 ++++-
 widget/src/lib.rs     |   3 +
 widget/src/stack.rs   | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 357 insertions(+), 2 deletions(-)
 create mode 100644 widget/src/stack.rs

diff --git a/core/src/layout.rs b/core/src/layout.rs
index 95720aba..98d05602 100644
--- a/core/src/layout.rs
+++ b/core/src/layout.rs
@@ -54,7 +54,7 @@ impl<'a> Layout<'a> {
     }
 
     /// Returns an iterator over the [`Layout`] of the children of a [`Node`].
-    pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
+    pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
         self.node.children().iter().map(move |node| {
             Layout::with_offset(
                 Vector::new(self.position.x, self.position.y),
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 61789c19..2afed3e6 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -21,7 +21,7 @@ use crate::text_input::{self, TextInput};
 use crate::toggler::{self, Toggler};
 use crate::tooltip::{self, Tooltip};
 use crate::vertical_slider::{self, VerticalSlider};
-use crate::{Column, MouseArea, Row, Space, Themer};
+use crate::{Column, MouseArea, Row, Space, Stack, Themer};
 
 use std::borrow::Borrow;
 use std::ops::RangeInclusive;
@@ -52,6 +52,19 @@ macro_rules! row {
     );
 }
 
+/// Creates a [`Stack`] with the given children.
+///
+/// [`Stack`]: crate::Stack
+#[macro_export]
+macro_rules! stack {
+    () => (
+        $crate::Stack::new()
+    );
+    ($($x:expr),+ $(,)?) => (
+        $crate::Stack::with_children([$($crate::core::Element::from($x)),+])
+    );
+}
+
 /// Creates a new [`Container`] with the provided content.
 ///
 /// [`Container`]: crate::Container
@@ -98,6 +111,18 @@ where
     Row::with_children(children)
 }
 
+/// Creates a new [`Stack`] with the given children.
+///
+/// [`Stack`]: crate::Stack
+pub fn stack<'a, Message, Theme, Renderer>(
+    children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
+) -> Stack<'a, Message, Theme, Renderer>
+where
+    Renderer: core::Renderer,
+{
+    Stack::with_children(children)
+}
+
 /// Creates a new [`Scrollable`] with the provided content.
 ///
 /// [`Scrollable`]: crate::Scrollable
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index 1eeacbae..00e9aaa4 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -12,6 +12,7 @@ mod column;
 mod mouse_area;
 mod row;
 mod space;
+mod stack;
 mod themer;
 
 pub mod button;
@@ -78,6 +79,8 @@ pub use slider::Slider;
 #[doc(no_inline)]
 pub use space::Space;
 #[doc(no_inline)]
+pub use stack::Stack;
+#[doc(no_inline)]
 pub use text::Text;
 #[doc(no_inline)]
 pub use text_editor::TextEditor;
diff --git a/widget/src/stack.rs b/widget/src/stack.rs
new file mode 100644
index 00000000..84f1f7ff
--- /dev/null
+++ b/widget/src/stack.rs
@@ -0,0 +1,327 @@
+//! Distribute content vertically.
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{Operation, Tree};
+use crate::core::{
+    Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
+};
+
+/// A container that displays children on top of each other.
+///
+/// The first [`Element`] dictates the intrinsic [`Size`] of a [`Stack`] and
+/// will be displayed as the base layer. Every consecutive [`Element`] will be
+/// renderer on top; on its own layer.
+///
+/// Keep in mind that too much layering will normally produce bad UX as well as
+/// introduce certain rendering overhead. Use this widget sparingly!
+#[allow(missing_debug_implementations)]
+pub struct Stack<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
+{
+    width: Length,
+    height: Length,
+    children: Vec<Element<'a, Message, Theme, Renderer>>,
+}
+
+impl<'a, Message, Theme, Renderer> Stack<'a, Message, Theme, Renderer>
+where
+    Renderer: crate::core::Renderer,
+{
+    /// Creates an empty [`Stack`].
+    pub fn new() -> Self {
+        Self::from_vec(Vec::new())
+    }
+
+    /// Creates a [`Stack`] with the given capacity.
+    pub fn with_capacity(capacity: usize) -> Self {
+        Self::from_vec(Vec::with_capacity(capacity))
+    }
+
+    /// Creates a [`Stack`] with the given elements.
+    pub fn with_children(
+        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
+    ) -> Self {
+        let iterator = children.into_iter();
+
+        Self::with_capacity(iterator.size_hint().0).extend(iterator)
+    }
+
+    /// Creates a [`Stack`] from an already allocated [`Vec`].
+    ///
+    /// Keep in mind that the [`Stack`] will not inspect the [`Vec`], which means
+    /// it won't automatically adapt to the sizing strategy of its contents.
+    ///
+    /// If any of the children have a [`Length::Fill`] strategy, you will need to
+    /// call [`Stack::width`] or [`Stack::height`] accordingly.
+    pub fn from_vec(
+        children: Vec<Element<'a, Message, Theme, Renderer>>,
+    ) -> Self {
+        Self {
+            width: Length::Shrink,
+            height: Length::Shrink,
+            children,
+        }
+    }
+
+    /// Sets the width of the [`Stack`].
+    pub fn width(mut self, width: impl Into<Length>) -> Self {
+        self.width = width.into();
+        self
+    }
+
+    /// Sets the height of the [`Stack`].
+    pub fn height(mut self, height: impl Into<Length>) -> Self {
+        self.height = height.into();
+        self
+    }
+
+    /// Adds an element to the [`Stack`].
+    pub fn push(
+        mut self,
+        child: impl Into<Element<'a, Message, Theme, Renderer>>,
+    ) -> Self {
+        let child = child.into();
+
+        if self.children.is_empty() {
+            let child_size = child.as_widget().size_hint();
+
+            self.width = self.width.enclose(child_size.width);
+            self.height = self.height.enclose(child_size.height);
+        }
+
+        self.children.push(child);
+        self
+    }
+
+    /// Adds an element to the [`Stack`], if `Some`.
+    pub fn push_maybe(
+        self,
+        child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
+    ) -> Self {
+        if let Some(child) = child {
+            self.push(child)
+        } else {
+            self
+        }
+    }
+
+    /// Extends the [`Stack`] with the given children.
+    pub fn extend(
+        self,
+        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
+    ) -> Self {
+        children.into_iter().fold(self, Self::push)
+    }
+}
+
+impl<'a, Message, Renderer> Default for Stack<'a, Message, Renderer>
+where
+    Renderer: crate::core::Renderer,
+{
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+    for Stack<'a, Message, Theme, Renderer>
+where
+    Renderer: crate::core::Renderer,
+{
+    fn children(&self) -> Vec<Tree> {
+        self.children.iter().map(Tree::new).collect()
+    }
+
+    fn diff(&self, tree: &mut Tree) {
+        tree.diff_children(&self.children);
+    }
+
+    fn size(&self) -> Size<Length> {
+        Size {
+            width: self.width,
+            height: self.height,
+        }
+    }
+
+    fn layout(
+        &self,
+        tree: &mut Tree,
+        renderer: &Renderer,
+        limits: &layout::Limits,
+    ) -> layout::Node {
+        if self.children.is_empty() {
+            return layout::Node::new(Size::ZERO);
+        }
+
+        let base = self.children[0].as_widget().layout(
+            &mut tree.children[0],
+            renderer,
+            limits,
+        );
+
+        let size = base.size();
+        let limits = layout::Limits::new(Size::ZERO, size);
+
+        let nodes = std::iter::once(base)
+            .chain(self.children[1..].iter().zip(&mut tree.children[1..]).map(
+                |(layer, tree)| {
+                    let node =
+                        layer.as_widget().layout(tree, renderer, &limits);
+
+                    node
+                },
+            ))
+            .collect();
+
+        layout::Node::with_children(size, nodes)
+    }
+
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        renderer: &Renderer,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        operation.container(None, layout.bounds(), &mut |operation| {
+            self.children
+                .iter()
+                .zip(&mut tree.children)
+                .zip(layout.children())
+                .for_each(|((child, state), layout)| {
+                    child
+                        .as_widget()
+                        .operate(state, layout, renderer, operation);
+                });
+        });
+    }
+
+    fn on_event(
+        &mut self,
+        tree: &mut Tree,
+        event: Event,
+        layout: Layout<'_>,
+        cursor: mouse::Cursor,
+        renderer: &Renderer,
+        clipboard: &mut dyn Clipboard,
+        shell: &mut Shell<'_, Message>,
+        viewport: &Rectangle,
+    ) -> event::Status {
+        self.children
+            .iter_mut()
+            .rev()
+            .zip(tree.children.iter_mut().rev())
+            .zip(layout.children().rev())
+            .map(|((child, state), layout)| {
+                child.as_widget_mut().on_event(
+                    state,
+                    event.clone(),
+                    layout,
+                    cursor,
+                    renderer,
+                    clipboard,
+                    shell,
+                    viewport,
+                )
+            })
+            .find(|&status| status == event::Status::Captured)
+            .unwrap_or(event::Status::Ignored)
+    }
+
+    fn mouse_interaction(
+        &self,
+        tree: &Tree,
+        layout: Layout<'_>,
+        cursor: mouse::Cursor,
+        viewport: &Rectangle,
+        renderer: &Renderer,
+    ) -> mouse::Interaction {
+        self.children
+            .iter()
+            .rev()
+            .zip(tree.children.iter().rev())
+            .zip(layout.children().rev())
+            .map(|((child, state), layout)| {
+                child.as_widget().mouse_interaction(
+                    state, layout, cursor, viewport, renderer,
+                )
+            })
+            .find(|&interaction| interaction != mouse::Interaction::Idle)
+            .unwrap_or_default()
+    }
+
+    fn draw(
+        &self,
+        tree: &Tree,
+        renderer: &mut Renderer,
+        theme: &Theme,
+        style: &renderer::Style,
+        layout: Layout<'_>,
+        cursor: mouse::Cursor,
+        viewport: &Rectangle,
+    ) {
+        if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
+            for (i, ((layer, state), layout)) in self
+                .children
+                .iter()
+                .zip(&tree.children)
+                .zip(layout.children())
+                .enumerate()
+            {
+                if i > 0 {
+                    renderer.with_layer(clipped_viewport, |renderer| {
+                        layer.as_widget().draw(
+                            state,
+                            renderer,
+                            theme,
+                            style,
+                            layout,
+                            cursor,
+                            &clipped_viewport,
+                        );
+                    });
+                } else {
+                    layer.as_widget().draw(
+                        state,
+                        renderer,
+                        theme,
+                        style,
+                        layout,
+                        cursor,
+                        &clipped_viewport,
+                    );
+                }
+            }
+        }
+    }
+
+    fn overlay<'b>(
+        &'b mut self,
+        tree: &'b mut Tree,
+        layout: Layout<'_>,
+        renderer: &Renderer,
+        translation: Vector,
+    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
+        overlay::from_children(
+            &mut self.children,
+            tree,
+            layout,
+            renderer,
+            translation,
+        )
+    }
+}
+
+impl<'a, Message, Theme, Renderer> From<Stack<'a, Message, Theme, Renderer>>
+    for Element<'a, Message, Theme, Renderer>
+where
+    Message: 'a,
+    Theme: 'a,
+    Renderer: crate::core::Renderer + 'a,
+{
+    fn from(stack: Stack<'a, Message, Theme, Renderer>) -> Self {
+        Self::new(stack)
+    }
+}
-- 
cgit