roast2d_internal 0.3.5

Roast2D internal crate
Documentation
use crate::prelude::*;

#[derive(Clone)]
pub struct LayoutContainer {
    pub bounds: Rect,
    pub padding: Padding,
    pub layout_type: LayoutType,
}

#[derive(Clone)]
pub struct Padding {
    pub left: f32,
    pub right: f32,
    pub top: f32,
    pub bottom: f32,
}

impl Padding {
    pub fn all(value: f32) -> Self {
        Self {
            left: value,
            right: value,
            top: value,
            bottom: value,
        }
    }

    pub fn horizontal_vertical(h: f32, v: f32) -> Self {
        Self {
            left: h,
            right: h,
            top: v,
            bottom: v,
        }
    }

    pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
        Self::horizontal_vertical(horizontal, vertical)
    }
}

#[derive(Clone)]
pub enum LayoutType {
    Vertical {
        gap: f32,
        alignment: Alignment,
    },
    Horizontal {
        gap: f32,
        alignment: Alignment,
    },
    Grid {
        columns: usize,
        gap: Vec2,
        alignment: Alignment,
    },
    Absolute,
}

#[derive(Clone, Copy, Debug)]
pub enum Alignment {
    Start,
    Center,
    End,
}

pub struct LayoutBuilder {
    container: LayoutContainer,
    items: Vec<LayoutItem>,
}

#[derive(Clone)]
pub struct LayoutItem {
    pub size: Vec2,
    pub position: Vec2,
    pub anchor: Vec2, // 0.0 = left/top, 0.5 = center, 1.0 = right/bottom
}

impl LayoutBuilder {
    pub fn new(bounds: Rect) -> Self {
        Self {
            container: LayoutContainer {
                bounds,
                padding: Padding::all(0.0),
                layout_type: LayoutType::Vertical {
                    gap: 0.0,
                    alignment: Alignment::Start,
                },
            },
            items: Vec::new(),
        }
    }

    pub fn with_padding(mut self, padding: Padding) -> Self {
        self.container.padding = padding;
        self
    }

    pub fn vertical(mut self, gap: f32) -> Self {
        self.container.layout_type = LayoutType::Vertical {
            gap,
            alignment: Alignment::Start,
        };
        self
    }

    pub fn horizontal(mut self, gap: f32) -> Self {
        self.container.layout_type = LayoutType::Horizontal {
            gap,
            alignment: Alignment::Start,
        };
        self
    }

    pub fn grid(mut self, columns: usize, gap: Vec2) -> Self {
        self.container.layout_type = LayoutType::Grid {
            columns,
            gap,
            alignment: Alignment::Start,
        };
        self
    }

    pub fn with_alignment(mut self, alignment: Alignment) -> Self {
        match &mut self.container.layout_type {
            LayoutType::Vertical { alignment: a, .. } => *a = alignment,
            LayoutType::Horizontal { alignment: a, .. } => *a = alignment,
            LayoutType::Grid { alignment: a, .. } => *a = alignment,
            LayoutType::Absolute => {}
        }
        self
    }

    pub fn add_item(mut self, size: Vec2) -> Self {
        self.items.push(LayoutItem {
            size,
            position: Vec2::ZERO,        // Will be calculated
            anchor: Vec2::new(0.5, 0.5), // Default center anchor
        });
        self
    }

    pub fn add_multiple_items(mut self, count: usize, size: Vec2) -> Self {
        for _ in 0..count {
            self = self.add_item(size);
        }
        self
    }

    pub fn calculate(self) -> LayoutResult {
        let layout_type = self.container.layout_type.clone();
        match layout_type {
            LayoutType::Vertical { gap, alignment } => {
                self.calculate_vertical_layout(gap, alignment)
            }
            LayoutType::Horizontal { gap, alignment } => {
                self.calculate_horizontal_layout(gap, alignment)
            }
            LayoutType::Grid {
                columns,
                gap,
                alignment,
            } => self.calculate_grid_layout(columns, gap, alignment),
            LayoutType::Absolute => LayoutResult { items: self.items },
        }
    }

    fn calculate_vertical_layout(mut self, gap: f32, alignment: Alignment) -> LayoutResult {
        let content_bounds = self.get_content_bounds();
        let mut current_y = content_bounds.max.y;

        for item in &mut self.items {
            let x = match alignment {
                Alignment::Start => content_bounds.min.x + item.size.x * 0.5,
                Alignment::Center => content_bounds.center().x,
                Alignment::End => content_bounds.max.x - item.size.x * 0.5,
            };

            current_y -= item.size.y * 0.5;
            item.position = Vec2::new(x, current_y);
            current_y -= item.size.y * 0.5 + gap;
        }

        LayoutResult { items: self.items }
    }

    fn calculate_horizontal_layout(mut self, gap: f32, alignment: Alignment) -> LayoutResult {
        let content_bounds = self.get_content_bounds();
        let mut current_x = content_bounds.min.x;

        for item in &mut self.items {
            let y = match alignment {
                Alignment::Start => content_bounds.max.y - item.size.y * 0.5,
                Alignment::Center => content_bounds.center().y,
                Alignment::End => content_bounds.min.y + item.size.y * 0.5,
            };

            current_x += item.size.x * 0.5;
            item.position = Vec2::new(current_x, y);
            current_x += item.size.x * 0.5 + gap;
        }

        LayoutResult { items: self.items }
    }

    fn calculate_grid_layout(
        mut self,
        columns: usize,
        gap: Vec2,
        _alignment: Alignment,
    ) -> LayoutResult {
        let content_bounds = self.get_content_bounds();

        for (index, item) in self.items.iter_mut().enumerate() {
            let row = index / columns;
            let col = index % columns;

            let x = content_bounds.min.x + col as f32 * (item.size.x + gap.x) + item.size.x * 0.5;
            let y = content_bounds.max.y - row as f32 * (item.size.y + gap.y) - item.size.y * 0.5;

            item.position = Vec2::new(x, y);
        }

        LayoutResult { items: self.items }
    }

    fn get_content_bounds(&self) -> Rect {
        let padding = &self.container.padding;
        Rect {
            min: Vec2::new(
                self.container.bounds.min.x + padding.left,
                self.container.bounds.min.y + padding.bottom,
            ),
            max: Vec2::new(
                self.container.bounds.max.x - padding.right,
                self.container.bounds.max.y - padding.top,
            ),
        }
    }
}

pub struct LayoutResult {
    pub items: Vec<LayoutItem>,
}