use std::ops::Range;
use omp_core::Str;
use smallvec::SmallVec;
use super::{
layout::{grid_measure, place_grid_row, solve_columns},
text::{Pre, TextLeaf, clip_start_runs, paint_rich},
};
use crate::{
component::{Cached, Component, IntoChildren, PaintCtx, Slot, next_slot},
context::UiContext,
frame::{Color, Rect, Style},
markup::{Align, Truncate},
props::{Prop, PropValue, Props},
rich::{Pipeline, RichSink, RichText},
};
pub struct Table {
props: Props,
slot: Slot,
children: Vec<Cached>,
rows: SmallVec<RowMeta, 8>,
bands: SmallVec<(u16, u16), 8>,
}
struct RowMeta {
cells: Range<usize>,
props: Props,
}
impl Table {
pub fn new() -> Self {
Self {
props: Props::new(),
slot: next_slot(),
children: Vec::new(),
rows: SmallVec::new(),
bands: SmallVec::new(),
}
}
pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
self.props.set(prop, value);
self
}
pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
self.props.set(prop, value);
self
}
pub fn row(mut self, row: TableRow) -> Self {
let start = self.children.len();
for cell in row.cells {
self.children.push(Cached::new(Box::new(cell)));
}
self
.rows
.push(RowMeta { cells: start..self.children.len(), props: row.props });
self
}
fn spans(&self) -> SmallVec<Range<usize>, 8> {
self.rows.iter().map(|row| row.cells.clone()).collect()
}
fn column_gap(&self) -> u16 {
if self.props.get(Prop::Gap).is_some() {
self.props.gap()
} else {
2
}
}
}
impl Default for Table {
fn default() -> Self {
Self::new()
}
}
impl Component for Table {
fn props(&self) -> &Props {
&self.props
}
fn props_mut(&mut self) -> &mut Props {
&mut self.props
}
fn slot(&self) -> Slot {
self.slot
}
fn children(&self) -> &[Cached] {
&self.children
}
fn children_mut(&mut self) -> &mut [Cached] {
&mut self.children
}
fn measure(&mut self, ctx: &UiContext) -> (u16, u16) {
let spans = self.spans();
let gap = self.column_gap();
grid_measure(ctx, &mut self.children, &spans, gap)
}
fn height(&mut self, ctx: &UiContext, width: u16) -> u16 {
let spans = self.spans();
let gap = self.column_gap();
let columns = solve_columns(ctx, &mut self.children, &spans, width, gap);
let mut height = 0_u16;
for span in &spans {
let tallest = span
.clone()
.enumerate()
.map(|(column, index)| self.children[index].height(ctx, columns[column].max(1)))
.max()
.unwrap_or(0)
.max(1);
height = height.saturating_add(tallest);
}
height
}
fn place(&mut self, ctx: &UiContext, content: Rect) {
let spans = self.spans();
let gap = self.column_gap();
let columns = solve_columns(ctx, &mut self.children, &spans, content.width, gap);
self.bands.clear();
let mut y = content.y;
for span in spans {
let row_height =
place_grid_row(ctx, &mut self.children, span, &columns, content.x, y, gap);
self.bands.push((y, row_height));
y = y.saturating_add(row_height);
}
}
fn paint(&mut self, pc: &mut PaintCtx<'_>, rect: Rect) {
for (row, &(top, height)) in self.rows.iter().zip(&self.bands) {
let background = row.props.style(&pc.ctx.theme).background_color();
if background != Color::Default && top < pc.clip {
let rows = height.min(pc.clip.saturating_sub(top));
pc.frame
.fill(Rect::new(rect.x, top, rect.width, rows), Style::new().bg(background));
}
}
for child in self.children.iter_mut().filter(|child| child.visible) {
child.paint(pc);
}
}
}
#[derive(Default)]
pub struct TableRow {
props: Props,
cells: SmallVec<TableCell, 8>,
}
impl TableRow {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
self.props.set(prop, value);
self
}
pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
self.props.set(prop, value);
self
}
pub fn cell(mut self, cell: TableCell) -> Self {
self.cells.push(cell);
self
}
}
pub struct TableCell {
props: Props,
slot: Slot,
children: Vec<Cached>,
rich: RichText,
shown: usize,
}
impl TableCell {
pub fn new() -> Self {
Self {
props: Props::new(),
slot: next_slot(),
children: Vec::new(),
rich: RichText::default(),
shown: 0,
}
}
pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
self.props.set(prop, value);
self
}
pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
self.props.set(prop, value);
self
}
pub fn child(mut self, children: impl IntoChildren) -> Self {
let first = self.children.len();
children.extend_children(&mut self.children);
for child in &mut self.children[first..] {
child.comp_mut().props_mut().set(Prop::Vertical, true);
child.invalidate();
}
self
}
fn truncates(&self) -> Option<Truncate> {
self.props.truncate()
}
fn flatten_run<'a>(child: &'a mut Cached, ctx: &UiContext) -> Option<(&'a Str, Style)> {
let style = child.comp().props().style(&ctx.theme);
let comp = child.comp_mut();
if let Some(pre) = comp.downcast_mut::<Pre>() {
return Some((pre.content(), style));
}
if let Some(text) = comp.downcast_mut::<TextLeaf>() {
return Some((text.content(), style));
}
None
}
fn flatten(&mut self, ctx: &UiContext, width: u16) -> bool {
let Some(mode) = self.truncates() else {
return false;
};
let mut runs: SmallVec<(Style, Str), 8> = SmallVec::new();
for child in self.children.iter_mut().filter(|child| child.visible) {
let Some((text, style)) = Self::flatten_run(child, ctx) else {
return false;
};
for (index, line) in text.as_str().split('\n').enumerate() {
if index > 0 {
runs.push((style, Str::new_static(" ")));
}
runs.push((style, text.slice_ref(line)));
}
}
self.rich.clear();
match mode {
Truncate::End => {
let mut clip = (&mut self.rich).clip(width.max(1), Some('…'));
for (style, text) in &runs {
clip.run(*style, text);
}
},
Truncate::Start => clip_start_runs(&mut self.rich, width, &runs),
}
true
}
}
impl Default for TableCell {
fn default() -> Self {
Self::new()
}
}
impl Component for TableCell {
fn props(&self) -> &Props {
&self.props
}
fn props_mut(&mut self) -> &mut Props {
&mut self.props
}
fn slot(&self) -> Slot {
self.slot
}
fn children(&self) -> &[Cached] {
&self.children
}
fn children_mut(&mut self) -> &mut [Cached] {
&mut self.children
}
fn measure(&mut self, ctx: &UiContext) -> (u16, u16) {
let mut min = 0_u16;
let mut natural = 0_u16;
for child in self.children.iter_mut().filter(|child| child.visible) {
let (child_min, child_natural) = child.measure(ctx);
min = min.saturating_add(child_min);
natural = natural.saturating_add(child_natural);
}
if self.truncates().is_some() {
return (natural.min(1), natural);
}
(min, natural)
}
fn height(&mut self, ctx: &UiContext, width: u16) -> u16 {
if self.truncates().is_some() {
return 1;
}
let mut remaining = width;
let mut tallest = 1_u16;
for child in self.children.iter_mut().filter(|child| child.visible) {
if remaining == 0 {
break;
}
let (_, child_natural) = child.measure(ctx);
let child_width = child_natural.min(remaining).max(1);
tallest = tallest.max(child.height(ctx, child_width));
remaining = remaining.saturating_sub(child_width);
}
tallest
}
fn place(&mut self, ctx: &UiContext, content: Rect) {
let mut widths: SmallVec<u16, 8> = SmallVec::new();
let mut remaining = content.width;
for child in self.children.iter_mut().filter(|child| child.visible) {
if remaining == 0 {
break;
}
let (_, child_natural) = child.measure(ctx);
let width = child_natural.min(remaining).max(1);
widths.push(width);
remaining = remaining.saturating_sub(width);
}
self.shown = widths.len();
let slack = remaining;
let mut cursor = content.x.saturating_add(match self.props.align() {
Align::Start => 0,
Align::Center => slack / 2,
Align::End => slack,
});
for (child, &width) in self
.children
.iter_mut()
.filter(|child| child.visible)
.zip(&widths)
{
let height = child.height(ctx, width).min(content.height.max(1));
child.place(ctx, Rect::new(cursor, content.y, width, height));
cursor = cursor.saturating_add(width);
}
}
fn paint(&mut self, pc: &mut PaintCtx<'_>, rect: Rect) {
if self.flatten(pc.ctx, rect.width) {
paint_rich(pc, rect, &self.rich, self.props.align());
return;
}
let mut placed = self.shown;
for child in self.children.iter_mut().filter(|child| child.visible) {
if placed == 0 {
break;
}
placed -= 1;
child.paint(pc);
}
}
}