1use std::ops::Range;
2
3use omp_core::Str;
4use smallvec::SmallVec;
5
6use super::{
7 layout::{grid_measure, place_grid_row, solve_columns},
8 text::{Pre, TextLeaf, clip_start_runs, paint_rich},
9};
10use crate::{
11 component::{Cached, Component, IntoChildren, PaintCtx, Slot, next_slot},
12 context::UiContext,
13 frame::{Color, Rect, Style},
14 markup::{Align, Truncate},
15 props::{Prop, PropValue, Props},
16 rich::{Pipeline, RichSink, RichText},
17};
18
19pub struct Table {
28 props: Props,
29 slot: Slot,
30 children: Vec<Cached>,
32 rows: SmallVec<RowMeta, 8>,
33 bands: SmallVec<(u16, u16), 8>,
35}
36
37struct RowMeta {
38 cells: Range<usize>,
39 props: Props,
40}
41
42impl Table {
43 pub fn new() -> Self {
45 Self {
46 props: Props::new(),
47 slot: next_slot(),
48 children: Vec::new(),
49 rows: SmallVec::new(),
50 bands: SmallVec::new(),
51 }
52 }
53
54 pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
56 self.props.set(prop, value);
57 self
58 }
59
60 pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
62 self.props.set(prop, value);
63 self
64 }
65
66 pub fn row(mut self, row: TableRow) -> Self {
68 let start = self.children.len();
69 for cell in row.cells {
70 self.children.push(Cached::new(Box::new(cell)));
71 }
72 self
73 .rows
74 .push(RowMeta { cells: start..self.children.len(), props: row.props });
75 self
76 }
77
78 fn spans(&self) -> SmallVec<Range<usize>, 8> {
79 self.rows.iter().map(|row| row.cells.clone()).collect()
80 }
81
82 fn column_gap(&self) -> u16 {
85 if self.props.get(Prop::Gap).is_some() {
86 self.props.gap()
87 } else {
88 2
89 }
90 }
91}
92
93impl Default for Table {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99impl Component for Table {
100 fn props(&self) -> &Props {
101 &self.props
102 }
103
104 fn props_mut(&mut self) -> &mut Props {
105 &mut self.props
106 }
107
108 fn slot(&self) -> Slot {
109 self.slot
110 }
111
112 fn children(&self) -> &[Cached] {
113 &self.children
114 }
115
116 fn children_mut(&mut self) -> &mut [Cached] {
117 &mut self.children
118 }
119
120 fn measure(&mut self, ctx: &UiContext) -> (u16, u16) {
121 let spans = self.spans();
122 let gap = self.column_gap();
123 grid_measure(ctx, &mut self.children, &spans, gap)
124 }
125
126 fn height(&mut self, ctx: &UiContext, width: u16) -> u16 {
127 let spans = self.spans();
128 let gap = self.column_gap();
129 let columns = solve_columns(ctx, &mut self.children, &spans, width, gap);
130 let mut height = 0_u16;
131 for span in &spans {
132 let tallest = span
133 .clone()
134 .enumerate()
135 .map(|(column, index)| self.children[index].height(ctx, columns[column].max(1)))
136 .max()
137 .unwrap_or(0)
138 .max(1);
139 height = height.saturating_add(tallest);
140 }
141 height
142 }
143
144 fn place(&mut self, ctx: &UiContext, content: Rect) {
145 let spans = self.spans();
146 let gap = self.column_gap();
147 let columns = solve_columns(ctx, &mut self.children, &spans, content.width, gap);
148 self.bands.clear();
149 let mut y = content.y;
150 for span in spans {
151 let row_height =
152 place_grid_row(ctx, &mut self.children, span, &columns, content.x, y, gap);
153 self.bands.push((y, row_height));
154 y = y.saturating_add(row_height);
155 }
156 }
157
158 fn paint(&mut self, pc: &mut PaintCtx<'_>, rect: Rect) {
159 for (row, &(top, height)) in self.rows.iter().zip(&self.bands) {
160 let background = row.props.style(&pc.ctx.theme).background_color();
161 if background != Color::Default && top < pc.clip {
162 let rows = height.min(pc.clip.saturating_sub(top));
163 pc.frame
164 .fill(Rect::new(rect.x, top, rect.width, rows), Style::new().bg(background));
165 }
166 }
167 for child in self.children.iter_mut().filter(|child| child.visible) {
168 child.paint(pc);
169 }
170 }
171}
172
173#[derive(Default)]
176pub struct TableRow {
177 props: Props,
178 cells: SmallVec<TableCell, 8>,
179}
180
181impl TableRow {
182 pub fn new() -> Self {
184 Self::default()
185 }
186
187 pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
189 self.props.set(prop, value);
190 self
191 }
192
193 pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
195 self.props.set(prop, value);
196 self
197 }
198
199 pub fn cell(mut self, cell: TableCell) -> Self {
201 self.cells.push(cell);
202 self
203 }
204}
205
206pub struct TableCell {
214 props: Props,
215 slot: Slot,
216 children: Vec<Cached>,
217 rich: RichText,
218 shown: usize,
221}
222
223impl TableCell {
224 pub fn new() -> Self {
226 Self {
227 props: Props::new(),
228 slot: next_slot(),
229 children: Vec::new(),
230 rich: RichText::default(),
231 shown: 0,
232 }
233 }
234
235 pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
237 self.props.set(prop, value);
238 self
239 }
240
241 pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
243 self.props.set(prop, value);
244 self
245 }
246
247 pub fn child(mut self, children: impl IntoChildren) -> Self {
249 let first = self.children.len();
250 children.extend_children(&mut self.children);
251 for child in &mut self.children[first..] {
252 child.comp_mut().props_mut().set(Prop::Vertical, true);
253 child.invalidate();
254 }
255 self
256 }
257
258 fn truncates(&self) -> Option<Truncate> {
259 self.props.truncate()
260 }
261
262 fn flatten_run<'a>(child: &'a mut Cached, ctx: &UiContext) -> Option<(&'a Str, Style)> {
265 let style = child.comp().props().style(&ctx.theme);
266 let comp = child.comp_mut();
267 if let Some(pre) = comp.downcast_mut::<Pre>() {
268 return Some((pre.content(), style));
269 }
270 if let Some(text) = comp.downcast_mut::<TextLeaf>() {
271 return Some((text.content(), style));
272 }
273 None
274 }
275
276 fn flatten(&mut self, ctx: &UiContext, width: u16) -> bool {
279 let Some(mode) = self.truncates() else {
280 return false;
281 };
282 let mut runs: SmallVec<(Style, Str), 8> = SmallVec::new();
285 for child in self.children.iter_mut().filter(|child| child.visible) {
286 let Some((text, style)) = Self::flatten_run(child, ctx) else {
287 return false;
288 };
289 for (index, line) in text.as_str().split('\n').enumerate() {
290 if index > 0 {
291 runs.push((style, Str::new_static(" ")));
292 }
293 runs.push((style, text.slice_ref(line)));
294 }
295 }
296 self.rich.clear();
297 match mode {
298 Truncate::End => {
299 let mut clip = (&mut self.rich).clip(width.max(1), Some('…'));
300 for (style, text) in &runs {
301 clip.run(*style, text);
302 }
303 },
304 Truncate::Start => clip_start_runs(&mut self.rich, width, &runs),
305 }
306 true
307 }
308}
309
310impl Default for TableCell {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316impl Component for TableCell {
317 fn props(&self) -> &Props {
318 &self.props
319 }
320
321 fn props_mut(&mut self) -> &mut Props {
322 &mut self.props
323 }
324
325 fn slot(&self) -> Slot {
326 self.slot
327 }
328
329 fn children(&self) -> &[Cached] {
330 &self.children
331 }
332
333 fn children_mut(&mut self) -> &mut [Cached] {
334 &mut self.children
335 }
336
337 fn measure(&mut self, ctx: &UiContext) -> (u16, u16) {
338 let mut min = 0_u16;
339 let mut natural = 0_u16;
340 for child in self.children.iter_mut().filter(|child| child.visible) {
341 let (child_min, child_natural) = child.measure(ctx);
342 min = min.saturating_add(child_min);
343 natural = natural.saturating_add(child_natural);
344 }
345 if self.truncates().is_some() {
346 return (natural.min(1), natural);
348 }
349 (min, natural)
350 }
351
352 fn height(&mut self, ctx: &UiContext, width: u16) -> u16 {
353 if self.truncates().is_some() {
354 return 1;
355 }
356 let mut remaining = width;
357 let mut tallest = 1_u16;
358 for child in self.children.iter_mut().filter(|child| child.visible) {
359 if remaining == 0 {
360 break;
361 }
362 let (_, child_natural) = child.measure(ctx);
363 let child_width = child_natural.min(remaining).max(1);
364 tallest = tallest.max(child.height(ctx, child_width));
365 remaining = remaining.saturating_sub(child_width);
366 }
367 tallest
368 }
369
370 fn place(&mut self, ctx: &UiContext, content: Rect) {
371 let mut widths: SmallVec<u16, 8> = SmallVec::new();
375 let mut remaining = content.width;
376 for child in self.children.iter_mut().filter(|child| child.visible) {
377 if remaining == 0 {
378 break;
379 }
380 let (_, child_natural) = child.measure(ctx);
381 let width = child_natural.min(remaining).max(1);
382 widths.push(width);
383 remaining = remaining.saturating_sub(width);
384 }
385 self.shown = widths.len();
386 let slack = remaining;
389 let mut cursor = content.x.saturating_add(match self.props.align() {
390 Align::Start => 0,
391 Align::Center => slack / 2,
392 Align::End => slack,
393 });
394 for (child, &width) in self
395 .children
396 .iter_mut()
397 .filter(|child| child.visible)
398 .zip(&widths)
399 {
400 let height = child.height(ctx, width).min(content.height.max(1));
401 child.place(ctx, Rect::new(cursor, content.y, width, height));
402 cursor = cursor.saturating_add(width);
403 }
404 }
405
406 fn paint(&mut self, pc: &mut PaintCtx<'_>, rect: Rect) {
407 if self.flatten(pc.ctx, rect.width) {
408 paint_rich(pc, rect, &self.rich, self.props.align());
409 return;
410 }
411 let mut placed = self.shown;
412 for child in self.children.iter_mut().filter(|child| child.visible) {
413 if placed == 0 {
414 break;
415 }
416 placed -= 1;
417 child.paint(pc);
418 }
419 }
420}