Skip to main content

agpu/layout/
grid.rs

1//! Grid layout — 2D grid with row/column spans and gap spacing.
2
3use crate::core::Rect;
4use crate::layout::{DesiredSize, Layout, SizeConstraints};
5
6/// Span configuration for a grid item.
7#[derive(Debug, Clone, Copy)]
8pub struct GridSpan {
9    pub col_span: usize,
10    pub row_span: usize,
11}
12
13impl Default for GridSpan {
14    fn default() -> Self {
15        Self {
16            col_span: 1,
17            row_span: 1,
18        }
19    }
20}
21
22/// Per-child grid placement.
23#[derive(Debug, Clone)]
24pub struct GridItem {
25    pub index: usize,
26    pub span: GridSpan,
27}
28
29impl GridItem {
30    pub fn new(index: usize) -> Self {
31        Self {
32            index,
33            span: GridSpan::default(),
34        }
35    }
36
37    pub fn col_span(mut self, span: usize) -> Self {
38        self.span.col_span = span;
39        self
40    }
41
42    pub fn row_span(mut self, span: usize) -> Self {
43        self.span.row_span = span;
44        self
45    }
46}
47
48/// A 2D grid layout with configurable columns, rows, and gaps.
49pub struct GridLayout {
50    columns: usize,
51    rows: usize,
52    gap: f32,
53    items: Vec<GridItem>,
54}
55
56impl GridLayout {
57    pub fn new(columns: usize, rows: usize) -> Self {
58        Self {
59            columns: columns.max(1),
60            rows: rows.max(1),
61            gap: 0.0,
62            items: Vec::new(),
63        }
64    }
65
66    pub fn gap(mut self, gap: f32) -> Self {
67        self.gap = gap;
68        self
69    }
70
71    pub fn item(mut self, item: GridItem) -> Self {
72        self.items.push(item);
73        self
74    }
75}
76
77impl Layout for GridLayout {
78    fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize {
79        if children.is_empty() {
80            return DesiredSize::zero();
81        }
82        let max_child_w = children.iter().map(|c| c.width).fold(0.0f32, f32::max);
83        let max_child_h = children.iter().map(|c| c.height).fold(0.0f32, f32::max);
84
85        let total_w =
86            max_child_w * self.columns as f32 + self.gap * (self.columns as f32 - 1.0).max(0.0);
87        let total_h = max_child_h * self.rows as f32 + self.gap * (self.rows as f32 - 1.0).max(0.0);
88
89        let (w, h) = constraints.clamp(total_w, total_h);
90        DesiredSize::new(w, h)
91    }
92
93    fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect> {
94        if children.is_empty() {
95            return vec![];
96        }
97
98        let col_w =
99            (area.width - self.gap * (self.columns as f32 - 1.0).max(0.0)) / self.columns as f32;
100        let row_h = (area.height - self.gap * (self.rows as f32 - 1.0).max(0.0)) / self.rows as f32;
101
102        // If we have explicit items, use them for span info
103        if !self.items.is_empty() {
104            return self.arrange_with_items(children, area, col_w, row_h);
105        }
106
107        // Default: fill row-by-row, each child gets one cell
108        let mut rects = Vec::with_capacity(children.len());
109        for (i, _child) in children.iter().enumerate() {
110            let col = i % self.columns;
111            let row = i / self.columns;
112            let x = area.x + col as f32 * (col_w + self.gap);
113            let y = area.y + row as f32 * (row_h + self.gap);
114            rects.push(Rect::new(x, y, col_w, row_h));
115        }
116        rects
117    }
118}
119
120impl GridLayout {
121    fn arrange_with_items(
122        &self,
123        children: &[DesiredSize],
124        area: Rect,
125        col_w: f32,
126        row_h: f32,
127    ) -> Vec<Rect> {
128        let mut rects = Vec::with_capacity(children.len());
129        let mut col = 0usize;
130        let mut row = 0usize;
131
132        for (i, _child) in children.iter().enumerate() {
133            let span = self
134                .items
135                .iter()
136                .find(|it| it.index == i)
137                .map(|it| it.span)
138                .unwrap_or_default();
139
140            let cs = span.col_span.min(self.columns - col);
141            let rs = span.row_span.min(self.rows - row);
142
143            let x = area.x + col as f32 * (col_w + self.gap);
144            let y = area.y + row as f32 * (row_h + self.gap);
145            let w = col_w * cs as f32 + self.gap * (cs as f32 - 1.0).max(0.0);
146            let h = row_h * rs as f32 + self.gap * (rs as f32 - 1.0).max(0.0);
147
148            rects.push(Rect::new(x, y, w, h));
149
150            col += cs;
151            if col >= self.columns {
152                col = 0;
153                row += 1;
154            }
155        }
156        rects
157    }
158}