cascada 0.3.0

A general purpose UI layout engine
Documentation
//! The layout engine uses [`IntrinsicSize`] and [`BoxConstraints`] to calculate the final
//! size and position of the layout nodes. The core idea of the layout engine is that there are
//! only three different sizes that a
//! [`Layout`] node will want to be:
//!
//! - A fixed size e.g. `500px`.
//! - As large as possible (usually this means filling the parent).
//! - As small as possible (usually this means fitting the children).
//!
//! These are represented by [`BoxSizing`]:
//!
//! - [`BoxSizing::Fixed`]: Fixed width or height.
//! - [`BoxSizing::Flex`]: Fill the available space.
//! - [`BoxSizing::Shrink`]: Be as small as possible.
//!
//! Thus [`IntrinsicSize`] size describes the preferred width and height of a layout
//! node.
//!
//! ```
//! use cascada::{IntrinsicSize,BoxSizing};
//!
//! let intrinsic_size = IntrinsicSize{
//!     width: BoxSizing::Shrink,
//!     height: BoxSizing::Flex(1),
//! };
//! ```
//!
//! The final width and height are dependent on the all the other factors in the layout tree.
//! For example, if two nodes in a [`HorizontalLayout`] both want to fill the parents width
//! then they will each have half the available width.
//!
//!```
//! use cascada::{HorizontalLayout, EmptyLayout, IntrinsicSize, solve_layout, Size, Layout};
//!
//! let child = EmptyLayout::new()
//!    .intrinsic_size(IntrinsicSize::fill());
//!
//! let mut layout = HorizontalLayout::from([child.clone(),child])
//!    .intrinsic_size(IntrinsicSize::fill());
//!
//! solve_layout(&mut layout,Size::unit(200.0));
//!
//! assert_eq!(layout.children()[0].size().width,100.0);
//! assert_eq!(layout.children()[1].size().width,100.0);
//!```
//!
//! ## Padding
//!
//! [`Padding`] is the space between the edges of a layout node and its contents. It is
//! functionally the same as the [`padding`] property in CSS.
//!
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/padding
use crate::{
    Bounds, BoxConstraints, BoxSizing, GlobalId, IntrinsicSize, LayoutError, Position, Size,
};
use std::fmt::Debug;

mod block;
mod empty;
mod horizontal;
mod vertical;

pub use block::BlockLayout;
pub use empty::EmptyLayout;
pub use horizontal::HorizontalLayout;
pub use vertical::VerticalLayout;

#[cfg(feature = "ffi")]
use crate::ffi::LayoutNode;

/// Calculates the final size and position of all the layout nodes. The
/// `window_size` is the maximum available space for the root node.
///
/// This function returns any layout errors such as overflow or out of bounds.
///
/// # Example
///
/// ```
/// use cascada::{solve_layout, BlockLayout, EmptyLayout, IntrinsicSize, Padding, Size};
///
/// let child = EmptyLayout::new()
///     .intrinsic_size(IntrinsicSize::fixed(50.0,50.0));
/// let mut block = BlockLayout::new(child)
///     .padding(Padding::all(10.0));
///
/// let errors = solve_layout(&mut block,Size::unit(500.0));
/// assert!(errors.is_empty());
/// ```
pub fn solve_layout(root: &mut dyn Layout, window_size: Size) -> Vec<LayoutError> {
    match root.get_intrinsic_size().width {
        BoxSizing::Fixed(width) => root.set_max_width(width),
        _ => {
            if root.constraints().max_width.is_none() {
                root.set_max_width(window_size.width);
            }
        }
    }

    match root.get_intrinsic_size().height {
        BoxSizing::Fixed(height) => root.set_max_height(height),
        _ => root.set_max_height(window_size.height),
    }

    // It's important that the min constraints are solved before the max constraints
    // because the min constraints are used in calculating max constraints.
    let _ = root.solve_min_constraints();
    root.solve_max_constraints();
    root.update_size();
    root.position_children();

    root.collect_errors()
}

/// A layout node.
pub trait Layout: Debug + private::Sealed {
    fn label(&self) -> String;

    /// Solve the minimum constraints of each [`Layout`] node recursively
    fn solve_min_constraints(&mut self) -> (f32, f32);

    /// Solve the max constraints for the children and pass them down the tree
    fn solve_max_constraints(&mut self);

    /// Position the layout nodes after size calculations.
    fn position_children(&mut self);

    /// Update the size of every [`LayoutNode`] based on it's size and constraints.
    fn update_size(&mut self);

    /// Collect all the errors from the node tree.
    fn collect_errors(&mut self) -> Vec<LayoutError>;

    /// Get the `id` of the [`Layout`]
    fn id(&self) -> GlobalId;

    /// Returns the [`BoxConstraints`] of the [`Layout`].
    fn constraints(&self) -> BoxConstraints;

    /// Returns the [`IntrinsicSize`] of the [`Layout`]
    fn get_intrinsic_size(&self) -> IntrinsicSize;

    /// Returns the [`Size`] of the [`Layout`]
    fn size(&self) -> Size;

    /// Returns the [`Position`] of the [`Layout`]
    fn position(&self) -> Position;

    /// Returns the [`Bounds`] of the [`Layout`]
    fn bounds(&self) -> Bounds {
        Bounds::new(self.position(), self.size())
    }

    fn children(&self) -> &[Box<dyn Layout>];

    fn set_max_width(&mut self, width: f32);
    fn set_max_height(&mut self, height: f32);
    fn set_min_width(&mut self, width: f32);
    fn set_min_height(&mut self, height: f32);

    fn set_position(&mut self, position: Position) {
        self.set_x(position.x);
        self.set_y(position.y);
    }

    fn set_x(&mut self, x: f32);
    fn set_y(&mut self, y: f32);

    /// Returns an iterator over the layout tree.
    fn iter(&self) -> LayoutIter<'_>;

    /// Searches for a [`Layout`] node with a matching [`GlobalId`].
    fn get(&self, id: GlobalId) -> Option<&dyn Layout> {
        self.iter().find(|&layout| layout.id() == id)
    }

    #[cfg(feature = "ffi")]
    fn as_layout_node(&self) -> LayoutNode {
        LayoutNode {
            id: self.id(),
            position: self.position(),
            size: self.size(),
        }
    }
}

mod private {
    pub trait Sealed {}

    impl Sealed for super::EmptyLayout {}
    impl Sealed for super::BlockLayout {}
    impl Sealed for super::HorizontalLayout {}
    impl Sealed for super::VerticalLayout {}
}

/// An `Iterator` over the layout tree.
pub struct LayoutIter<'a> {
    stack: Vec<&'a dyn Layout>,
}

impl<'a> Iterator for LayoutIter<'a> {
    type Item = &'a dyn Layout;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(layout) = self.stack.pop() {
            let children = layout.children();
            let m = children.iter().map(|child| child.as_ref());
            self.stack.extend(m.rev());
            return Some(layout);
        }

        None
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn root_max_width() {
        let mut layout = EmptyLayout::new()
            .max_width(20.0)
            .intrinsic_size(IntrinsicSize::fill());

        solve_layout(&mut layout, Size::unit(200.0));
        assert_eq!(layout.size().width, 20.0);
    }
}