use blinc_core::Color;
use blinc_theme::{ColorToken, ThemeState};
use crate::div::{div, Div};
use crate::text::{text, Text};
fn header_bg() -> Color {
ThemeState::get().color(ColorToken::SurfaceOverlay)
}
fn border_color() -> Color {
ThemeState::get().color(ColorToken::Border)
}
fn header_text_color() -> Color {
ThemeState::get().color(ColorToken::TextPrimary)
}
fn cell_text_color() -> Color {
ThemeState::get().color(ColorToken::TextSecondary)
}
fn striped_bg() -> Color {
ThemeState::get().color(ColorToken::AccentSubtle)
}
const CELL_PADDING: f32 = 12.0;
const DEFAULT_FONT_SIZE: f32 = 14.0;
pub fn table() -> Div {
div().flex_col().overflow_clip()
}
pub fn thead() -> Div {
div().flex_col().bg(header_bg())
}
pub fn tbody() -> Div {
div().flex_col()
}
pub fn tfoot() -> Div {
div().flex_col().bg(header_bg())
}
pub fn tr() -> Div {
div().flex_row().w_full()
}
pub struct TableCell {
inner: Div,
}
impl TableCell {
fn new() -> Self {
Self {
inner: div()
.flex_row()
.items_center()
.flex_1() .padding_x_px(CELL_PADDING)
.padding_y_px(CELL_PADDING),
}
}
pub fn child(mut self, child: impl crate::div::ElementBuilder + 'static) -> Self {
self.inner = self.inner.child(child);
self
}
pub fn w(mut self, px: f32) -> Self {
self.inner = self.inner.w(px).flex_shrink_0();
self
}
pub fn flex_weight(mut self, weight: f32) -> Self {
self.inner = self.inner.flex_grow();
let _ = weight; self
}
pub fn w_fit(mut self) -> Self {
self.inner = self.inner.w_fit();
self
}
pub fn p(mut self, units: f32) -> Self {
self.inner = self.inner.p(units);
self
}
pub fn padding_px(mut self, px: f32) -> Self {
self.inner = self.inner.padding_x_px(px).padding_y_px(px);
self
}
pub fn px(mut self, units: f32) -> Self {
self.inner = self.inner.px(units);
self
}
pub fn py(mut self, units: f32) -> Self {
self.inner = self.inner.py(units);
self
}
pub fn bg(mut self, color: Color) -> Self {
self.inner = self.inner.bg(color);
self
}
pub fn justify_center(mut self) -> Self {
self.inner = self.inner.justify_center();
self
}
pub fn justify_end(mut self) -> Self {
self.inner = self.inner.justify_end();
self
}
pub fn items_center(mut self) -> Self {
self.inner = self.inner.items_center();
self
}
pub fn with_separator(self) -> Div {
div()
.flex_row()
.child(self)
.child(div().w(1.0).h_full().bg(border_color()))
}
pub fn into_div(self) -> Div {
self.inner
}
}
impl crate::div::ElementBuilder for TableCell {
fn build(&self, tree: &mut crate::tree::LayoutTree) -> crate::tree::LayoutNodeId {
self.inner.build(tree)
}
fn render_props(&self) -> crate::element::RenderProps {
self.inner.render_props()
}
fn children_builders(&self) -> &[Box<dyn crate::div::ElementBuilder>] {
self.inner.children_builders()
}
fn element_type_id(&self) -> crate::div::ElementTypeId {
self.inner.element_type_id()
}
fn semantic_type_name(&self) -> Option<&'static str> {
Some("td")
}
}
pub fn th(content: impl Into<String>) -> TableCell {
let txt = text(content)
.size(DEFAULT_FONT_SIZE)
.color(header_text_color())
.bold();
TableCell::new().child(txt)
}
pub fn td(content: impl Into<String>) -> TableCell {
let txt = text(content)
.size(DEFAULT_FONT_SIZE)
.color(cell_text_color());
TableCell::new().child(txt)
}
pub fn cell() -> TableCell {
TableCell::new()
}
pub fn striped_tr(index: usize) -> Div {
let bg = if index % 2 == 0 {
Color::TRANSPARENT
} else {
striped_bg()
};
tr().bg(bg)
}
pub struct TableBuilder {
headers: Vec<String>,
rows: Vec<Vec<String>>,
striped: bool,
header_bg: Color,
border_color: Color,
}
impl TableBuilder {
pub fn new() -> Self {
Self {
headers: Vec::new(),
rows: Vec::new(),
striped: false,
header_bg: header_bg(),
border_color: border_color(),
}
}
pub fn headers(mut self, headers: &[&str]) -> Self {
self.headers = headers.iter().map(|s| s.to_string()).collect();
self
}
pub fn row(mut self, cells: &[&str]) -> Self {
self.rows
.push(cells.iter().map(|s| s.to_string()).collect());
self
}
pub fn striped(mut self, enabled: bool) -> Self {
self.striped = enabled;
self
}
pub fn header_bg(mut self, color: Color) -> Self {
self.header_bg = color;
self
}
pub fn border_color(mut self, color: Color) -> Self {
self.border_color = color;
self
}
pub fn build(self) -> Div {
let mut tbl = table();
if !self.headers.is_empty() {
let mut header_row = tr();
for h in &self.headers {
header_row = header_row.child(th(h.as_str()));
}
tbl = tbl.child(thead().bg(self.header_bg).child(header_row));
}
if !self.rows.is_empty() {
let mut body = tbody();
for (i, row_data) in self.rows.iter().enumerate() {
let mut row = if self.striped { striped_tr(i) } else { tr() };
for cell_data in row_data {
row = row.child(td(cell_data.as_str()));
}
body = body.child(row);
}
tbl = tbl.child(body);
}
tbl
}
}
impl Default for TableBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn th_text(content: impl Into<String>) -> Text {
text(content)
.size(DEFAULT_FONT_SIZE)
.color(header_text_color())
.bold()
}
pub fn td_text(content: impl Into<String>) -> Text {
text(content)
.size(DEFAULT_FONT_SIZE)
.color(cell_text_color())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::div::ElementBuilder;
use crate::tree::LayoutTree;
fn init_theme() {
let _ = ThemeState::try_get().unwrap_or_else(|| {
ThemeState::init_default();
ThemeState::get()
});
}
#[test]
fn test_simple_table() {
init_theme();
let mut tree = LayoutTree::new();
let tbl = table()
.child(thead().child(tr().child(th("Header"))))
.child(tbody().child(tr().child(td("Data"))));
tbl.build(&mut tree);
assert!(!tree.is_empty());
}
#[test]
fn test_table_builder() {
init_theme();
let mut tree = LayoutTree::new();
let tbl = TableBuilder::new()
.headers(&["A", "B", "C"])
.row(&["1", "2", "3"])
.row(&["4", "5", "6"])
.striped(true)
.build();
tbl.build(&mut tree);
assert!(!tree.is_empty());
}
#[test]
fn test_cell_methods() {
init_theme();
let mut tree = LayoutTree::new();
let cell = td("Test")
.w(100.0)
.justify_end()
.bg(Color::from_hex(0x333333));
cell.build(&mut tree);
assert!(!tree.is_empty());
}
}