anathema-default-widgets 0.2.2

Default widget implementations for Anathema
Documentation
use std::ops::ControlFlow;

use anathema_geometry::Size;
use anathema_widgets::LayoutForEach;
use anathema_widgets::error::Result;
use anathema_widgets::layout::{Constraints, LayoutCtx};

use super::{Axis, Direction, expand, spacers};

pub(crate) struct SizeMod {
    inner: Size,
    max_size: Size,
    axis: Axis,
}

impl SizeMod {
    const ZERO: Self = Self::new(Size::ZERO, Axis::Vertical);

    const fn new(max_size: Size, axis: Axis) -> Self {
        Self {
            inner: Size::ZERO,
            max_size,
            axis,
        }
    }

    fn apply(&mut self, size: Size) {
        match self.axis {
            Axis::Vertical => {
                self.inner.width = self.inner.width.max(size.width);
                self.inner.height = (self.inner.height + size.height).min(self.max_size.height);
            }
            Axis::Horizontal => {
                self.inner.height = self.inner.height.max(size.height);
                self.inner.width = (self.inner.width + size.width).min(self.max_size.width);
            }
        }
    }

    fn no_space_left(&self) -> bool {
        match self.axis {
            Axis::Horizontal => self.inner.width >= self.max_size.width,
            Axis::Vertical => self.inner.height >= self.max_size.height,
        }
    }

    fn to_constraints(&self) -> Constraints {
        match self.axis {
            Axis::Horizontal => Constraints::new(self.max_size.width - self.inner.width, self.max_size.height),
            Axis::Vertical => Constraints::new(self.max_size.width, self.max_size.height - self.inner.height),
        }
    }

    pub fn inner_size(&self) -> Size {
        self.inner
    }
}

pub struct Many {
    pub direction: Direction,
    pub axis: Axis,
    unconstrained: bool,
    pub(crate) used_size: SizeMod,
}

impl Many {
    pub fn new(direction: Direction, axis: Axis, unconstrained: bool) -> Self {
        Self {
            direction,
            axis,
            unconstrained,
            used_size: SizeMod::ZERO,
        }
    }
}

impl Many {
    pub(crate) fn layout<'bp>(
        &mut self,
        mut children: LayoutForEach<'_, 'bp>,
        constraints: Constraints,
        ctx: &mut LayoutCtx<'_, 'bp>,
    ) -> Result<Size> {
        let max_constraints = constraints;

        self.used_size.axis = self.axis;
        self.used_size.max_size = Size::new(max_constraints.max_width(), max_constraints.max_height());

        let mut size = Size::ZERO;

        _ = children.each(ctx, |ctx, node, children| {
            if ["spacer", "expand"].contains(&node.ident) {
                return Ok(ControlFlow::Continue(()));
            }

            let widget_constraints = {
                let mut constraints = self.used_size.to_constraints();
                if self.unconstrained {
                    match self.axis {
                        Axis::Vertical => constraints.unbound_height(),
                        Axis::Horizontal => constraints.unbound_width(),
                    }
                }
                constraints
            };

            let widget_size = node.layout(children, widget_constraints, ctx)?.into();

            self.used_size.apply(widget_size);

            match self.used_size.no_space_left() {
                true => Ok(ControlFlow::Break(())),
                false => Ok(ControlFlow::Continue(())),
            }
        })?;

        let constraints = self.used_size.to_constraints();
        let expanded_size = expand::layout_all_expansions(&mut children, constraints, self.axis, ctx)?;
        self.used_size.apply(expanded_size);

        let constraints = self.used_size.to_constraints();
        let spacer_size = spacers::layout_all_spacers(&mut children, constraints, self.axis, ctx)?;
        self.used_size.apply(spacer_size);

        size.width = self.used_size.inner.width.max(max_constraints.min_width);
        size.height = (self.used_size.inner.height).max(max_constraints.min_height);

        match self.axis {
            Axis::Vertical => {
                size.width = self.used_size.inner.width.max(max_constraints.min_width);
                size.height = (self.used_size.inner.height).max(max_constraints.min_height);
            }
            Axis::Horizontal => {
                size.height = size.height.max(self.used_size.inner.height);
                size.height = size
                    .height
                    .max(self.used_size.inner.height)
                    .max(max_constraints.min_height);
                size.width = size
                    .width
                    .max(self.used_size.inner.width)
                    .max(max_constraints.min_width);
            }
        }

        if let Direction::Backward = self.direction {
            match self.axis {
                Axis::Horizontal => size.width = max_constraints.max_width(),
                Axis::Vertical => size.height = max_constraints.max_height(),
            }
        }

        Ok(size)
    }
}