use densa_text::{GlyphMap, LineMetrics};
use std::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Align {
Left,
Right,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Column {
pub title: Cow<'static, str>,
pub chars: u32,
pub align: Align,
}
impl Column {
pub fn new(title: impl Into<Cow<'static, str>>, chars: u32, align: Align) -> Self {
Self {
title: title.into(),
chars,
align,
}
}
}
const GAP_CHARS: f32 = 2.0;
const MIN_PADDING: u32 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ColumnGeometry {
x: u32,
width: u32,
chars: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GridLayout {
rows: u32,
row_height: u32,
padding: u32,
gap: u32,
char_width: f32,
baseline: u32,
columns: Box<[ColumnGeometry]>,
width: u32,
}
impl GridLayout {
pub fn new(columns: &[Column], rows: u32, glyphs: &mut GlyphMap) -> Self {
let char_width = glyphs.get('0').map_or(glyphs.px() * 0.5, |g| g.advance);
let metrics: LineMetrics = glyphs.line_metrics();
let (ascent, line_height) = (metrics.ascent, metrics.line_height());
let row_height = line_height.ceil().max(1.0) as u32;
let baseline = ((row_height as f32 - line_height) / 2.0 + ascent).round() as u32;
let gap = (GAP_CHARS * char_width).ceil() as u32;
let padding = MIN_PADDING.max(gap.div_ceil(2));
let mut x = padding;
let columns: Box<[ColumnGeometry]> = columns
.iter()
.map(|c| {
let width = (c.chars as f32 * char_width).ceil() as u32;
let geometry = ColumnGeometry {
x,
width,
chars: c.chars,
};
x = x.saturating_add(width).saturating_add(gap);
geometry
})
.collect();
let content = columns
.last()
.map_or(0, |c| c.x.saturating_add(c.width) - padding);
Self {
rows,
row_height,
padding,
gap,
char_width,
baseline,
columns,
width: content.saturating_add(2 * padding),
}
}
pub fn rows(&self) -> u32 {
self.rows
}
pub fn column_count(&self) -> usize {
self.columns.len()
}
pub fn row_height(&self) -> u32 {
self.row_height
}
pub fn padding(&self) -> u32 {
self.padding
}
pub fn gap(&self) -> u32 {
self.gap
}
pub fn char_width(&self) -> f32 {
self.char_width
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.padding * 2 + (self.rows + 1) * self.row_height
}
pub fn column_x(&self, index: usize) -> u32 {
self.columns[index].x
}
pub fn column_width(&self, index: usize) -> u32 {
self.columns[index].width
}
pub fn column_chars(&self, index: usize) -> u32 {
self.columns[index].chars
}
pub fn row_y(&self, row: u32) -> u32 {
self.padding + row * self.row_height
}
pub fn baseline_y(&self, row: u32) -> f32 {
(self.row_y(row) + self.baseline) as f32
}
pub fn max_instances(&self) -> usize {
let per_row: usize = self
.columns
.iter()
.map(|c| c.chars as usize + 1)
.sum::<usize>()
+ 1;
(self.rows as usize + 1)
.saturating_mul(per_row)
.saturating_add(1)
}
}