use std::sync::Arc;
use crate::callback::{Callback, KeyHandler};
use crate::core::element::{Element, ElementKind};
use crate::core::event::{KeyEvent, MouseEvent};
use crate::style::{BorderStyle, Length, Padding, ScrollbarConfig, Style, StyleSlot};
use crate::utils::gradient::{ColorGradient, GradientRange};
use crate::widgets::scroll::{ScrollKeymap, scroll_action_from_key};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TableEvent {
pub index: usize,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum TableRowRole {
#[default]
Normal,
Section,
Separator,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TableDisclosureState {
Collapsed,
Expanded,
}
#[derive(Clone, Debug, Default)]
pub struct TableCell {
pub(crate) content: Arc<str>,
pub(crate) style: Style,
}
impl TableCell {
pub fn new(content: impl Into<Arc<str>>) -> Self {
Self {
content: content.into(),
style: Style::default(),
}
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn heat_fg(
mut self,
value: u64,
gradient: ColorGradient,
range: impl Into<GradientRange>,
) -> Self {
let color = gradient.color_for(value, range);
self.style = self.style.patch(Style::new().fg(color));
self
}
pub fn heat_bg(
mut self,
value: u64,
gradient: ColorGradient,
range: impl Into<GradientRange>,
) -> Self {
let color = gradient.color_for(value, range);
self.style = self.style.patch(Style::new().bg(color));
self
}
}
impl<T: Into<Arc<str>>> From<T> for TableCell {
fn from(value: T) -> Self {
Self::new(value)
}
}
#[derive(Clone, Debug, Default)]
pub struct TableRow {
pub(crate) cells: Vec<TableCell>,
pub(crate) style: Style,
pub(crate) height: u16,
pub(crate) bottom_margin: u16,
pub(crate) role: TableRowRole,
pub(crate) depth: u16,
pub(crate) disclosure: Option<TableDisclosureState>,
}
impl TableRow {
pub fn new(cells: impl IntoIterator<Item = impl Into<TableCell>>) -> Self {
Self {
cells: cells.into_iter().map(Into::into).collect(),
style: Style::default(),
height: 1,
bottom_margin: 0,
role: TableRowRole::Normal,
depth: 0,
disclosure: None,
}
}
pub fn key_value(key: impl Into<TableCell>, value: impl Into<TableCell>) -> Self {
Self::new([key.into(), value.into()])
}
pub fn section(title: impl Into<TableCell>) -> Self {
Self::new([title.into()]).role(TableRowRole::Section)
}
pub fn separator() -> Self {
Self::new(std::iter::empty::<TableCell>()).role(TableRowRole::Separator)
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn height(mut self, height: u16) -> Self {
self.height = height;
self
}
pub fn auto_height(mut self) -> Self {
self.height = 0;
self
}
pub fn bottom_margin(mut self, margin: u16) -> Self {
self.bottom_margin = margin;
self
}
pub fn role(mut self, role: TableRowRole) -> Self {
self.role = role;
self
}
pub fn depth(mut self, depth: u16) -> Self {
self.depth = depth;
self
}
pub fn disclosure(mut self, disclosure: TableDisclosureState) -> Self {
self.disclosure = Some(disclosure);
self
}
}
pub(crate) fn resolved_row_height(row: &TableRow) -> u16 {
if row.height > 0 {
return row.height;
}
let mut max_lines = 1u16;
for cell in &row.cells {
let lines = cell.content.as_ref().lines().count().max(1) as u16;
max_lines = max_lines.max(lines);
}
max_lines
}
pub(crate) fn resolved_row_total_height(row: &TableRow) -> u16 {
resolved_row_height(row).saturating_add(row.bottom_margin)
}
pub(crate) fn table_header_reserved_height(
header: Option<&TableRow>,
rows_len: usize,
row_gap: u16,
) -> u16 {
header
.map(resolved_row_total_height)
.unwrap_or(0)
.saturating_add(if header.is_some() && rows_len > 0 {
row_gap
} else {
0
})
}
pub(crate) fn row_index_at_visual_offset(
rows: &[TableRow],
offset: usize,
visual_y: u16,
row_gap: u16,
) -> Option<usize> {
if rows.is_empty() || offset >= rows.len() {
return None;
}
let mut remaining = visual_y;
for (index, row) in rows.iter().enumerate().skip(offset) {
let row_h = resolved_row_total_height(row).max(1);
if remaining < row_h {
return Some(index);
}
remaining = remaining.saturating_sub(row_h);
if index + 1 < rows.len() {
if remaining < row_gap {
return None;
}
remaining = remaining.saturating_sub(row_gap);
}
}
None
}
pub(crate) fn visible_rows_for_height(
rows: &[TableRow],
offset: usize,
available_height: u16,
row_gap: u16,
) -> usize {
if available_height == 0 || rows.is_empty() || offset >= rows.len() {
return 0;
}
let mut used = 0u16;
let mut count = 0usize;
for (idx, row) in rows.iter().enumerate().skip(offset) {
let row_h = resolved_row_total_height(row).max(1);
let gap_before = if count > 0 && idx > offset {
row_gap
} else {
0
};
let needed = gap_before.saturating_add(row_h);
if used.saturating_add(needed) > available_height {
break;
}
used = used.saturating_add(needed);
count = count.saturating_add(1);
}
if count == 0 { 1 } else { count }
}
impl<I, C> From<I> for TableRow
where
I: IntoIterator<Item = C>,
C: Into<TableCell>,
{
fn from(iter: I) -> Self {
Self::new(iter)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ColumnWidth {
Fixed(u16),
Percent(u16),
Min(u16),
Max(u16),
Fill(u16),
}
#[derive(Clone)]
pub struct Table {
pub(crate) rows: Arc<[TableRow]>,
pub(crate) header: Option<TableRow>,
pub(crate) widths: Vec<ColumnWidth>,
pub(crate) column_styles: Vec<Style>,
pub(crate) row_styles: Vec<Style>,
pub(crate) selected: usize,
pub(crate) column_spacing: u16,
pub(crate) row_gap: u16,
pub(crate) style: Style,
pub(crate) hover_style: StyleSlot,
pub(crate) item_hover_style: StyleSlot,
pub(crate) alternating_row_style: Option<Style>,
pub(crate) row_style_full_width: bool,
pub(crate) selection_style: StyleSlot,
pub(crate) selection_symbol: Option<Arc<str>>,
pub(crate) selection_symbol_style: Option<Style>,
pub(crate) unselected_symbol: Option<Arc<str>>,
pub(crate) border: bool,
pub(crate) border_style: BorderStyle,
pub(crate) padding: Padding,
pub(crate) scrollbar: bool,
pub(crate) scrollbar_config: ScrollbarConfig,
pub(crate) scroll_keys: ScrollKeymap,
pub(crate) scroll_wheel: bool,
pub(crate) width: Length,
pub(crate) height: Length,
pub(crate) on_select: Option<Callback<TableEvent>>,
pub(crate) on_activate: Option<Callback<TableEvent>>,
pub(crate) on_click: Option<Callback<MouseEvent>>,
pub(crate) on_scroll_to: Option<Callback<usize>>,
pub(crate) on_key: Option<KeyHandler>,
pub(crate) disabled: bool,
pub(crate) disabled_style: Style,
pub(crate) focusable: bool,
pub(crate) show_scroll_indicators: bool,
pub(crate) scroll_indicator_style: Style,
pub(crate) inspector: bool,
pub(crate) inspector_key_style: Style,
pub(crate) inspector_value_style: Style,
pub(crate) inspector_section_style: Style,
pub(crate) inspector_separator_style: Style,
pub(crate) inspector_indent_size: u16,
pub(crate) inspector_collapsed_symbol: Arc<str>,
pub(crate) inspector_expanded_symbol: Arc<str>,
pub(crate) inspector_separator_char: char,
}
impl Default for Table {
fn default() -> Self {
Self {
rows: Arc::new([]),
header: None,
widths: Vec::new(),
column_styles: Vec::new(),
row_styles: Vec::new(),
selected: 0,
column_spacing: 1,
row_gap: 0,
style: Style::default(),
hover_style: StyleSlot::Inherit,
item_hover_style: StyleSlot::Inherit,
alternating_row_style: None,
row_style_full_width: false,
selection_style: StyleSlot::Inherit,
selection_symbol: None,
selection_symbol_style: None,
unselected_symbol: None,
border: false,
border_style: BorderStyle::Plain,
padding: Padding::default(),
scrollbar: false,
scrollbar_config: ScrollbarConfig::default(),
scroll_keys: ScrollKeymap::default(),
scroll_wheel: true,
width: Length::Flex(1),
height: Length::Flex(1),
on_select: None,
on_activate: None,
on_click: None,
on_scroll_to: None,
on_key: None,
disabled: false,
disabled_style: Style::default(),
focusable: true,
show_scroll_indicators: false,
scroll_indicator_style: Style::default(),
inspector: false,
inspector_key_style: Style::default(),
inspector_value_style: Style::default(),
inspector_section_style: Style::default(),
inspector_separator_style: Style::default(),
inspector_indent_size: 2,
inspector_collapsed_symbol: Arc::from("â–¸"),
inspector_expanded_symbol: Arc::from("â–¾"),
inspector_separator_char: '─',
}
}
}
impl Table {
pub fn new() -> Self {
Self::default()
}
pub fn rows(mut self, rows: impl IntoIterator<Item = impl Into<TableRow>>) -> Self {
self.rows = rows.into_iter().map(Into::into).collect::<Vec<_>>().into();
self
}
pub fn row(mut self, row: impl Into<TableRow>) -> Self {
let mut rows = self.rows.to_vec();
rows.push(row.into());
self.rows = rows.into();
self
}
pub fn header(mut self, header: impl Into<TableRow>) -> Self {
self.header = Some(header.into());
self
}
pub fn header_style(mut self, style: Style) -> Self {
if let Some(header) = &mut self.header {
header.style = header.style.patch(style);
}
self
}
pub fn row_style(mut self, style: Style) -> Self {
let mut rows = self.rows.to_vec();
for row in &mut rows {
row.style = row.style.patch(style);
}
self.rows = rows.into();
self
}
pub fn column_style(mut self, index: usize, style: Style) -> Self {
if self.column_styles.len() <= index {
self.column_styles
.resize(index.saturating_add(1), Style::default());
}
self.column_styles[index] = self.column_styles[index].patch(style);
self
}
pub fn column_styles(mut self, styles: impl IntoIterator<Item = Style>) -> Self {
self.column_styles = styles.into_iter().collect();
self
}
pub fn row_style_at(mut self, index: usize, style: Style) -> Self {
if self.row_styles.len() <= index {
self.row_styles
.resize(index.saturating_add(1), Style::default());
}
self.row_styles[index] = self.row_styles[index].patch(style);
self
}
pub fn row_styles(mut self, styles: impl IntoIterator<Item = Style>) -> Self {
self.row_styles = styles.into_iter().collect();
self
}
pub fn widths(mut self, widths: impl IntoIterator<Item = ColumnWidth>) -> Self {
self.widths = widths.into_iter().collect();
self
}
pub fn selected(mut self, selected: usize) -> Self {
self.selected = selected;
self
}
pub fn column_spacing(mut self, spacing: u16) -> Self {
self.column_spacing = spacing;
self
}
pub fn row_gap(mut self, gap: u16) -> Self {
self.row_gap = gap;
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn hover_style(mut self, style: Style) -> Self {
self.hover_style = StyleSlot::Replace(style);
self
}
pub fn extend_hover_style(mut self, style: Style) -> Self {
self.hover_style = StyleSlot::Extend(style);
self
}
pub fn inherit_hover_style(mut self) -> Self {
self.hover_style = StyleSlot::Inherit;
self
}
pub fn item_hover_style(mut self, style: Style) -> Self {
self.item_hover_style = StyleSlot::Replace(style);
self
}
pub fn extend_item_hover_style(mut self, style: Style) -> Self {
self.item_hover_style = StyleSlot::Extend(style);
self
}
pub fn inherit_item_hover_style(mut self) -> Self {
self.item_hover_style = StyleSlot::Inherit;
self
}
pub fn alternating_row_style(mut self, style: Style) -> Self {
self.alternating_row_style = Some(style);
self
}
pub fn row_style_full_width(mut self, full_width: bool) -> Self {
self.row_style_full_width = full_width;
self
}
pub fn selection_style(mut self, style: Style) -> Self {
self.selection_style = StyleSlot::Replace(style);
self
}
pub fn extend_selection_style(mut self, style: Style) -> Self {
self.selection_style = StyleSlot::Extend(style);
self
}
pub fn inherit_selection_style(mut self) -> Self {
self.selection_style = StyleSlot::Inherit;
self
}
pub fn selection_symbol(mut self, symbol: Option<impl Into<Arc<str>>>) -> Self {
self.selection_symbol = symbol.map(Into::into);
self
}
pub fn selection_symbol_style(mut self, style: Style) -> Self {
self.selection_symbol_style = Some(style);
self
}
pub fn unselected_symbol(mut self, symbol: Option<impl Into<Arc<str>>>) -> Self {
self.unselected_symbol = symbol.map(Into::into);
self
}
pub fn border(mut self, border: bool) -> Self {
self.border = border;
self
}
pub fn border_style(mut self, style: BorderStyle) -> Self {
self.border_style = style;
self
}
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
pub fn scrollbar(mut self, scrollbar: bool) -> Self {
self.scrollbar = scrollbar;
self
}
pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
self.scrollbar_config = config;
self
}
pub fn scroll_keys(mut self, keys: ScrollKeymap) -> Self {
self.scroll_keys = keys;
self
}
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
pub fn on_select(mut self, cb: Callback<TableEvent>) -> Self {
self.on_select = Some(cb);
self
}
pub fn on_activate(mut self, cb: Callback<TableEvent>) -> Self {
self.on_activate = Some(cb);
self
}
pub fn on_click(mut self, cb: Callback<MouseEvent>) -> Self {
self.on_click = Some(cb);
self
}
pub fn on_scroll_to(mut self, cb: Callback<usize>) -> Self {
self.on_scroll_to = Some(cb);
self
}
pub fn on_key(mut self, handler: KeyHandler) -> Self {
self.on_key = Some(handler);
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn focusable(mut self, focusable: bool) -> Self {
self.focusable = focusable;
self
}
pub fn show_scroll_indicators(mut self, show: bool) -> Self {
self.show_scroll_indicators = show;
self
}
pub fn scroll_indicator_style(mut self, style: Style) -> Self {
self.scroll_indicator_style = style;
self
}
pub fn inspector(mut self, enabled: bool) -> Self {
self.inspector = enabled;
self
}
pub fn inspector_preset(mut self) -> Self {
self.inspector = true;
if self.widths.is_empty() {
self.widths = vec![ColumnWidth::Min(24), ColumnWidth::Fill(1)];
}
self
}
pub fn inspector_key_style(mut self, style: Style) -> Self {
self.inspector_key_style = style;
self
}
pub fn inspector_value_style(mut self, style: Style) -> Self {
self.inspector_value_style = style;
self
}
pub fn inspector_section_style(mut self, style: Style) -> Self {
self.inspector_section_style = style;
self
}
pub fn inspector_separator_style(mut self, style: Style) -> Self {
self.inspector_separator_style = style;
self
}
pub fn inspector_indent_size(mut self, size: u16) -> Self {
self.inspector_indent_size = size.max(1);
self
}
pub fn inspector_disclosure_symbols(
mut self,
collapsed: impl Into<Arc<str>>,
expanded: impl Into<Arc<str>>,
) -> Self {
self.inspector_collapsed_symbol = collapsed.into();
self.inspector_expanded_symbol = expanded.into();
self
}
pub fn inspector_separator_char(mut self, separator_char: char) -> Self {
self.inspector_separator_char = separator_char;
self
}
pub(crate) fn next_selection(
selected: usize,
len: usize,
key: &KeyEvent,
scroll_keys: ScrollKeymap,
) -> Option<usize> {
let action = scroll_action_from_key(key, scroll_keys)?;
crate::widgets::list::List::selection_for_action_in_len(selected, len, action)
}
}
impl From<Table> for Element {
fn from(value: Table) -> Self {
Element::new(ElementKind::Table(Box::new(value)))
}
}
impl crate::layout::hash::LayoutHash for Table {
fn layout_hash(
&self,
hasher: &mut impl std::hash::Hasher,
_recurse: &dyn Fn(&Element) -> Option<u64>,
) -> Option<()> {
use std::hash::Hash;
self.width.hash(hasher);
self.height.hash(hasher);
self.border.hash(hasher);
self.border_style.hash(hasher);
self.padding.hash(hasher);
self.row_gap.hash(hasher);
let needs_content = matches!(self.height, Length::Auto);
if needs_content {
self.rows.len().hash(hasher);
if let Some(header) = &self.header {
header.height.hash(hasher);
header.bottom_margin.hash(hasher);
}
for row in self.rows.iter() {
row.height.hash(hasher);
row.bottom_margin.hash(hasher);
}
}
self.header.is_some().hash(hasher);
self.column_spacing.hash(hasher);
self.scrollbar.hash(hasher);
self.scrollbar_config.gap.hash(hasher);
self.show_scroll_indicators.hash(hasher);
self.widths.hash(hasher);
self.selected.hash(hasher);
Some(())
}
}
mod layout;
mod node;
mod reconcile;
mod shared;
pub(crate) use layout::measure_table;
pub(crate) use node::TableNode;
pub(crate) use reconcile::reconcile_table;
pub(crate) use shared::{
TableBorderLineKind, distribute_extra_width, shrink_widths_to_fit, table_border_glyphs,
table_border_line, table_fixed_chars, table_render_width,
};