use serde::{Deserialize, Serialize};
use crate::layout::Constraint;
use crate::node::{ActionId, Common, IntoNode, Key, Node};
use crate::style::Style;
use crate::text::Text;
use crate::widgets::list::{HighlightSpacing, ListState};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Cell {
Text(Text),
Full {
content: Text,
#[serde(default, skip_serializing_if = "Option::is_none")]
style: Option<Style>,
},
}
impl Cell {
#[must_use]
pub fn new(content: impl Into<Text>) -> Self {
Self::Full {
content: content.into(),
style: None,
}
}
#[must_use]
pub fn style(mut self, style: Style) -> Self {
if let Self::Full { style: slot, .. } = &mut self {
*slot = Some(style);
}
self
}
#[must_use]
pub fn content(&self) -> &Text {
match self {
Self::Text(text) => text,
Self::Full { content, .. } => content,
}
}
}
impl From<Text> for Cell {
fn from(text: Text) -> Self {
Self::Text(text)
}
}
impl From<&str> for Cell {
fn from(s: &str) -> Self {
Self::Text(Text::from(s))
}
}
impl From<String> for Cell {
fn from(s: String) -> Self {
Self::Text(Text::from(s))
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Row {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<Key>,
pub cells: Vec<Cell>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub height: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bottom_margin: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub style: Option<Style>,
}
impl Row {
#[must_use]
pub fn new(cells: impl IntoIterator<Item = impl Into<Cell>>) -> Self {
Self {
cells: cells.into_iter().map(Into::into).collect(),
..Self::default()
}
}
#[must_use]
pub fn key(mut self, key: impl AsRef<str>) -> Self {
self.key = Some(key.as_ref().to_owned());
self
}
#[must_use]
pub fn height(mut self, height: u16) -> Self {
self.height = Some(height);
self
}
#[must_use]
pub fn bottom_margin(mut self, margin: u16) -> Self {
self.bottom_margin = Some(margin);
self
}
#[must_use]
pub fn style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Sticky {
#[default]
FirstColumn,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Table {
#[serde(flatten)]
pub common: Common,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<Row>,
pub rows: Vec<Row>,
#[serde(skip_serializing_if = "Option::is_none")]
pub footer: Option<Row>,
pub widths: Vec<Constraint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_spacing: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub row_highlight_style: Option<Style>,
#[serde(skip_serializing_if = "Option::is_none")]
pub highlight_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub highlight_spacing: Option<HighlightSpacing>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<ListState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sticky: Option<Sticky>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scrollbar: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on_select: Option<ActionId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on_activate: Option<ActionId>,
}
impl Table {
#[must_use]
pub fn new(
rows: impl IntoIterator<Item = impl Into<Row>>,
widths: impl IntoIterator<Item = Constraint>,
) -> Self {
Self {
rows: rows.into_iter().map(Into::into).collect(),
widths: widths.into_iter().collect(),
..Self::default()
}
}
#[must_use]
pub fn header(mut self, header: impl Into<Row>) -> Self {
self.header = Some(header.into());
self
}
#[must_use]
pub fn footer(mut self, footer: impl Into<Row>) -> Self {
self.footer = Some(footer.into());
self
}
#[must_use]
pub fn widths(mut self, widths: impl IntoIterator<Item = Constraint>) -> Self {
self.widths = widths.into_iter().collect();
self
}
#[must_use]
pub fn column_spacing(mut self, spacing: u16) -> Self {
self.column_spacing = Some(spacing);
self
}
#[must_use]
pub fn row_highlight_style(mut self, style: Style) -> Self {
self.row_highlight_style = Some(style);
self
}
#[must_use]
pub fn highlight_symbol(mut self, symbol: impl Into<String>) -> Self {
self.highlight_symbol = Some(symbol.into());
self
}
#[must_use]
pub fn highlight_spacing(mut self, spacing: HighlightSpacing) -> Self {
self.highlight_spacing = Some(spacing);
self
}
#[must_use]
pub fn state(mut self, state: ListState) -> Self {
self.state = Some(state);
self
}
#[must_use]
pub fn sticky(mut self, sticky: Sticky) -> Self {
self.sticky = Some(sticky);
self
}
#[must_use]
pub fn scrollbar(mut self, scrollbar: bool) -> Self {
self.scrollbar = Some(scrollbar);
self
}
#[must_use]
pub fn on_select(mut self, action: impl Into<ActionId>) -> Self {
self.on_select = Some(action.into());
self
}
#[must_use]
pub fn on_activate(mut self, action: impl Into<ActionId>) -> Self {
self.on_activate = Some(action.into());
self
}
}
impl IntoNode for Table {}
impl From<Table> for Node {
fn from(t: Table) -> Self {
Node::Table(t)
}
}