1use densa_text::{GlyphMap, LineMetrics};
2use std::borrow::Cow;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum Align {
8 Left,
10 Right,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub struct Column {
19 pub title: Cow<'static, str>,
21 pub chars: u32,
23 pub align: Align,
25}
26
27impl Column {
28 pub fn new(title: impl Into<Cow<'static, str>>, chars: u32, align: Align) -> Self {
30 Self {
31 title: title.into(),
32 chars,
33 align,
34 }
35 }
36}
37
38const GAP_CHARS: f32 = 2.0;
40const MIN_PADDING: u32 = 8;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44struct ColumnGeometry {
45 x: u32,
46 width: u32,
47 chars: u32,
48}
49
50#[derive(Debug, Clone, PartialEq)]
55pub struct GridLayout {
56 rows: u32,
57 row_height: u32,
58 padding: u32,
59 gap: u32,
60 char_width: f32,
61 baseline: u32,
63 columns: Box<[ColumnGeometry]>,
64 width: u32,
65}
66
67impl GridLayout {
68 pub fn new(columns: &[Column], rows: u32, glyphs: &mut GlyphMap) -> Self {
70 let char_width = glyphs.get('0').map_or(glyphs.px() * 0.5, |g| g.advance);
71 let metrics: LineMetrics = glyphs.line_metrics();
72 let (ascent, line_height) = (metrics.ascent, metrics.line_height());
73 let row_height = line_height.ceil().max(1.0) as u32;
75 let baseline = ((row_height as f32 - line_height) / 2.0 + ascent).round() as u32;
76 let gap = (GAP_CHARS * char_width).ceil() as u32;
77 let padding = MIN_PADDING.max(gap.div_ceil(2));
79
80 let mut x = padding;
81 let columns: Box<[ColumnGeometry]> = columns
82 .iter()
83 .map(|c| {
84 let width = (c.chars as f32 * char_width).ceil() as u32;
85 let geometry = ColumnGeometry {
86 x,
87 width,
88 chars: c.chars,
89 };
90 x = x.saturating_add(width).saturating_add(gap);
92 geometry
93 })
94 .collect();
95 let content = columns
96 .last()
97 .map_or(0, |c| c.x.saturating_add(c.width) - padding);
98 Self {
99 rows,
100 row_height,
101 padding,
102 gap,
103 char_width,
104 baseline,
105 columns,
106 width: content.saturating_add(2 * padding),
107 }
108 }
109
110 pub fn rows(&self) -> u32 {
112 self.rows
113 }
114
115 pub fn column_count(&self) -> usize {
117 self.columns.len()
118 }
119
120 pub fn row_height(&self) -> u32 {
122 self.row_height
123 }
124
125 pub fn padding(&self) -> u32 {
127 self.padding
128 }
129
130 pub fn gap(&self) -> u32 {
132 self.gap
133 }
134
135 pub fn char_width(&self) -> f32 {
137 self.char_width
138 }
139
140 pub fn width(&self) -> u32 {
142 self.width
143 }
144
145 pub fn height(&self) -> u32 {
147 self.padding * 2 + (self.rows + 1) * self.row_height
148 }
149
150 pub fn column_x(&self, index: usize) -> u32 {
155 self.columns[index].x
156 }
157
158 pub fn column_width(&self, index: usize) -> u32 {
163 self.columns[index].width
164 }
165
166 pub fn column_chars(&self, index: usize) -> u32 {
171 self.columns[index].chars
172 }
173
174 pub fn row_y(&self, row: u32) -> u32 {
176 self.padding + row * self.row_height
177 }
178
179 pub fn baseline_y(&self, row: u32) -> f32 {
181 (self.row_y(row) + self.baseline) as f32
182 }
183
184 pub fn max_instances(&self) -> usize {
187 let per_row: usize = self
188 .columns
189 .iter()
190 .map(|c| c.chars as usize + 1)
191 .sum::<usize>()
192 + 1;
193 (self.rows as usize + 1)
194 .saturating_mul(per_row)
195 .saturating_add(1)
196 }
197}