use vello_cpu::kurbo::{Rect, Size};
use super::LayoutContext;
use crate::model::{Grid, Position, TableSeries};
#[derive(Debug, Clone)]
pub struct GridDefinition {
pub index: usize,
pub config: Grid,
pub min_size: Size,
pub auto_resize: bool,
}
impl GridDefinition {
pub fn new(index: usize, config: Grid) -> Self {
Self {
index,
config,
min_size: Size::new(100.0, 100.0), auto_resize: false,
}
}
pub fn with_table_series(mut self, table: &TableSeries) -> Self {
let header_height = if table.header_config.show {
table.header_config.height
} else {
0.0
};
let body_height = if table.body_config.show {
table.data.len() as f64 * table.body_config.row_height
} else {
0.0
};
let padding = 16.0;
self.min_size = Size::new(
200.0, header_height + body_height + padding,
);
self.auto_resize = table.auto_fit_grid;
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct GridRect {
pub index: usize,
pub bounds: Rect,
pub inner_bounds: Rect,
}
pub struct GridManager {
context: LayoutContext,
}
impl GridManager {
pub fn new(context: LayoutContext) -> Self {
Self { context }
}
pub fn compute_layout(&self, grids: &[GridDefinition], container: Rect) -> Vec<GridRect> {
if grids.is_empty() {
return Vec::new();
}
let mut raw_rects: Vec<(usize, Rect)> = grids
.iter()
.map(|grid| {
let bounds = self.calculate_grid_bounds(grid, container);
(grid.index, bounds)
})
.collect();
for (i, grid) in grids.iter().enumerate() {
if grid.auto_resize {
let (_, ref mut bounds) = raw_rects[i];
let min_width = grid.min_size.width;
let min_height = grid.min_size.height;
if bounds.width() < min_width {
let extra = min_width - bounds.width();
bounds.x1 += extra;
}
if bounds.height() < min_height {
let extra = min_height - bounds.height();
bounds.y1 += extra;
}
}
}
self.resolve_conflicts(&mut raw_rects, container);
raw_rects
.into_iter()
.map(|(index, bounds)| {
let inner_bounds = self.calculate_inner_bounds(bounds, container);
GridRect {
index,
bounds,
inner_bounds,
}
})
.collect()
}
fn calculate_grid_bounds(&self, grid: &GridDefinition, container: Rect) -> Rect {
let chart_width = container.width();
let chart_height = container.height();
let left = match &grid.config.left {
Position::Value(v) => container.x0 + v,
Position::Percent(pct) => container.x0 + chart_width * pct / 100.0,
Position::Auto => container.x0 + self.context.padding,
_ => container.x0 + self.context.padding,
};
let right = match &grid.config.right {
Position::Value(v) => container.x1 - v,
Position::Percent(pct) => container.x1 - chart_width * pct / 100.0,
Position::Auto => container.x1 - self.context.padding,
_ => container.x1 - self.context.padding,
};
let top = match &grid.config.top {
Position::Value(v) => container.y0 + v,
Position::Percent(pct) => container.y0 + chart_height * pct / 100.0,
Position::Auto => container.y0 + self.context.padding,
_ => container.y0 + self.context.padding,
};
let bottom = match &grid.config.bottom {
Position::Value(v) => container.y1 - v,
Position::Percent(pct) => container.y1 - chart_height * pct / 100.0,
Position::Auto => container.y1 - self.context.padding,
_ => container.y1 - self.context.padding,
};
let left = left.min(right - 50.0).max(container.x0);
let right = right.max(left + 50.0).min(container.x1);
let top = top.min(bottom - 50.0).max(container.y0);
let bottom = bottom.max(top + 50.0).min(container.y1);
Rect::new(left, top, right, bottom)
}
fn calculate_inner_bounds(&self, bounds: Rect, _container: Rect) -> Rect {
let padding = 5.0;
Rect::new(
bounds.x0 + padding,
bounds.y0 + padding,
bounds.x1 - padding,
bounds.y1 - padding,
)
}
fn resolve_conflicts(&self, rects: &mut [(usize, Rect)], container: Rect) {
let n = rects.len();
if n <= 1 {
return;
}
for i in 0..n {
for j in (i + 1)..n {
let rect_i = rects[i].1;
let rect_j = rects[j].1;
if Self::rects_overlap(rect_i, rect_j) {
let overlap_y = rect_i.y1 - rect_j.y0;
rects[j].1.y0 += overlap_y + self.context.spacing;
rects[j].1.y1 += overlap_y + self.context.spacing;
if rects[j].1.y1 > container.y1 {
rects[j].1.y0 = container.y0 + self.context.padding;
rects[j].1.y1 -= overlap_y + self.context.spacing;
let overlap_x = rect_i.x1 - rect_j.x0;
rects[j].1.x0 += overlap_x + self.context.spacing;
rects[j].1.x1 += overlap_x + self.context.spacing;
}
}
}
}
}
fn rects_overlap(a: Rect, b: Rect) -> bool {
a.x0 < b.x1 && a.x1 > b.x0 && a.y0 < b.y1 && a.y1 > b.y0
}
pub fn auto_layout(
&self,
grids: &[GridDefinition],
container: Rect,
rows: usize,
cols: usize,
gap: f64,
) -> Vec<GridRect> {
if grids.is_empty() || rows == 0 || cols == 0 {
return Vec::new();
}
let available_width = container.width() - self.context.padding * 2.0;
let available_height = container.height() - self.context.padding * 2.0;
let cell_width = (available_width - gap * (cols - 1) as f64) / cols as f64;
let cell_height = (available_height - gap * (rows - 1) as f64) / rows as f64;
grids
.iter()
.enumerate()
.map(|(i, grid)| {
let row = i / cols;
let col = i % cols;
let x0 = container.x0 + self.context.padding + col as f64 * (cell_width + gap);
let y0 = container.y0 + self.context.padding + row as f64 * (cell_height + gap);
let x1 = x0 + cell_width;
let y1 = y0 + cell_height;
let bounds = Rect::new(x0, y0, x1, y1);
let inner_bounds = self.calculate_inner_bounds(bounds, container);
GridRect {
index: grid.index,
bounds,
inner_bounds,
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_grid(left: Position, right: Position, top: Position, bottom: Position) -> Grid {
Grid {
left,
right,
top,
bottom,
contain_label: false,
}
}
fn create_test_context() -> LayoutContext {
LayoutContext::new(800.0, 600.0)
}
#[test]
fn test_calculate_grid_bounds() {
let context = create_test_context();
let manager = GridManager::new(context);
let grid = GridDefinition::new(
0,
create_test_grid(
Position::Value(10.0),
Position::Value(10.0),
Position::Value(10.0),
Position::Value(10.0),
),
);
let container = Rect::new(0.0, 0.0, 800.0, 600.0);
let bounds = manager.calculate_grid_bounds(&grid, container);
assert_eq!(bounds.x0, 10.0);
assert_eq!(bounds.x1, 790.0);
assert_eq!(bounds.y0, 10.0);
assert_eq!(bounds.y1, 590.0);
}
#[test]
fn test_auto_layout() {
let context = create_test_context();
let manager = GridManager::new(context);
let grids = vec![
GridDefinition::new(
0,
create_test_grid(
Position::Auto,
Position::Auto,
Position::Auto,
Position::Auto,
),
),
GridDefinition::new(
1,
create_test_grid(
Position::Auto,
Position::Auto,
Position::Auto,
Position::Auto,
),
),
GridDefinition::new(
2,
create_test_grid(
Position::Auto,
Position::Auto,
Position::Auto,
Position::Auto,
),
),
GridDefinition::new(
3,
create_test_grid(
Position::Auto,
Position::Auto,
Position::Auto,
Position::Auto,
),
),
];
let container = Rect::new(0.0, 0.0, 800.0, 600.0);
let layout = manager.auto_layout(&grids, container, 2, 2, 10.0);
assert_eq!(layout.len(), 4);
assert_eq!(layout[0].bounds.x0, 10.0);
assert_eq!(layout[0].bounds.y0, 10.0);
}
}