diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d11b1d2..7599b9ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] - ReleaseDate + +### Changed + +- Improved styling of toggled table rows + ## [0.12.0] - 2024-01-07 ### Added diff --git a/src/tui/view/common/table.rs b/src/tui/view/common/table.rs index e78bd1b8..db328188 100644 --- a/src/tui/view/common/table.rs +++ b/src/tui/view/common/table.rs @@ -1,14 +1,26 @@ -use crate::tui::{context::TuiContext, view::draw::Generate}; +use crate::tui::{ + context::TuiContext, + view::{common::Checkbox, draw::Generate}, +}; +use itertools::Itertools; use ratatui::{ prelude::Constraint, + style::Styled, + text::Text, widgets::{Block, Cell, Row}, }; +use std::{iter, marker::PhantomData}; -/// Tabular data display with a static number of columns +/// Tabular data display with a static number of columns. +/// +/// The `R` generic defines the row type, which should be either an array of +/// cell types (e.g. `[Text; 3]`) or [ratatui::widgets::Row]. If using an array, +/// the length should match `COLS`. Allowing `Row` makes it possible to override +/// styling on a row-by-row basis. #[derive(Debug)] -pub struct Table<'a, const COLS: usize, Rows> { +pub struct Table<'a, const COLS: usize, R> { pub title: Option<&'a str>, - pub rows: Rows, + pub rows: Vec, /// Optional header row. Length should match column length pub header: Option<[&'a str; COLS]>, /// Use a different styling for alternating rows @@ -31,28 +43,48 @@ impl<'a, const COLS: usize, Rows: Default> Default for Table<'a, COLS, Rows> { } } -impl<'a, const COLS: usize, Cll, Rows> Generate for Table<'a, COLS, Rows> +impl<'a, const COLS: usize, Cll> Generate for Table<'a, COLS, [Cll; COLS]> where Cll: Into>, - Rows: IntoIterator, { type Output<'this> = ratatui::widgets::Table<'this> where Self: 'this; + fn generate<'this>(self) -> Self::Output<'this> + where + Self: 'this, + { + let table = Table { + title: self.title, + alternate_row_style: self.alternate_row_style, + header: self.header, + column_widths: self.column_widths, + rows: self.rows.into_iter().map(Row::new).collect_vec(), + }; + table.generate() + } +} + +impl<'a, const COLS: usize> Generate for Table<'a, COLS, Row<'a>> { + type Output<'this> = ratatui::widgets::Table<'this> + where + Self: 'this; + fn generate<'this>(self) -> Self::Output<'this> where Self: 'this, { let theme = &TuiContext::get().theme; let rows = self.rows.into_iter().enumerate().map(|(i, row)| { - // Alternate row style for readability - let style = if self.alternate_row_style && i % 2 == 1 { - theme.table_alt_text_style + // Apply theme styles, but let the row's individual styles override + let base_style = if self.alternate_row_style && i % 2 == 1 { + theme.table_alt_style } else { theme.table_text_style }; - Row::new(row).style(style) + let row_style = Styled::style(&row); + row.set_style(base_style.patch(row_style)) }); let mut table = ratatui::widgets::Table::new(rows, self.column_widths) .highlight_style(theme.table_highlight_style); @@ -75,3 +107,56 @@ where table } } + +/// A row in a table that can be toggled on/off. This will generate the checkbox +/// column, and apply the appropriate row styling. +#[derive(Debug)] +pub struct ToggleRow<'a, Cells> { + /// Needed to attach the lifetime of this value to the lifetime of the + /// generated row + phantom: PhantomData<&'a ()>, + cells: Cells, + enabled: bool, +} + +impl<'a, Cells> ToggleRow<'a, Cells> { + pub fn new(cells: Cells, enabled: bool) -> Self { + Self { + phantom: PhantomData, + cells, + enabled, + } + } +} + +impl<'a, Cells> Generate for ToggleRow<'a, Cells> +where + Cells: IntoIterator, + Cells::Item: Into>, +{ + type Output<'this> = Row<'this> + where + Self: 'this; + + fn generate<'this>(self) -> Self::Output<'this> + where + Self: 'this, + { + let theme = &TuiContext::get().theme; + // Include the given cells, then tack on the checkbox for enabled state + Row::new( + self.cells.into_iter().map(Cell::from).chain(iter::once( + Checkbox { + checked: self.enabled, + } + .generate() + .into(), + )), + ) + .style(if self.enabled { + theme.table_text_style + } else { + theme.table_disabled_style + }) + } +} diff --git a/src/tui/view/component/help.rs b/src/tui/view/component/help.rs index 82555ef1..403cdb27 100644 --- a/src/tui/view/component/help.rs +++ b/src/tui/view/component/help.rs @@ -98,7 +98,7 @@ impl Draw for HelpModal { // Collection metadata let collection_metadata = Table { title: Some("General"), - rows: [ + rows: vec![ [ Line::from("Configuration"), Line::from(tui_context.config.path().display().to_string()) diff --git a/src/tui/view/component/recipe.rs b/src/tui/view/component/recipe.rs index 7701af56..24e397f0 100644 --- a/src/tui/view/component/recipe.rs +++ b/src/tui/view/component/recipe.rs @@ -6,9 +6,12 @@ use crate::{ input::Action, view::{ common::{ - actions::ActionsModal, table::Table, tabs::Tabs, - template_preview::TemplatePreview, text_window::TextWindow, - Checkbox, Pane, + actions::ActionsModal, + table::{Table, ToggleRow}, + tabs::Tabs, + template_preview::TemplatePreview, + text_window::TextWindow, + Pane, }, component::primary::PrimaryPane, draw::{Draw, Generate, ToStringGenerate}, @@ -29,8 +32,7 @@ use derive_more::Display; use itertools::Itertools; use ratatui::{ prelude::{Constraint, Direction, Rect}, - text::Text, - widgets::{Paragraph, TableState}, + widgets::{Paragraph, Row, TableState}, Frame, }; use serde::{Deserialize, Serialize}; @@ -395,20 +397,17 @@ impl RowState { fn to_table<'a>( state: &'a SelectState, header: [&'a str; 3], -) -> Table<'a, 3, Vec<[Text<'a>; 3]>> { +) -> Table<'a, 3, Row<'a>> { Table { rows: state .items() .iter() .map(|item| { - [ - item.key.as_str().into(), - item.value.generate(), - Checkbox { - checked: *item.enabled, - } - .generate(), - ] + ToggleRow::new( + [item.key.as_str().into(), item.value.generate()], + *item.enabled, + ) + .generate() }) .collect_vec(), header: Some(header), @@ -417,7 +416,6 @@ fn to_table<'a>( Constraint::Percentage(50), Constraint::Min(3), ], - alternate_row_style: true, ..Default::default() } } diff --git a/src/tui/view/theme.rs b/src/tui/view/theme.rs index 49855f61..1f899b90 100644 --- a/src/tui/view/theme.rs +++ b/src/tui/view/theme.rs @@ -25,7 +25,8 @@ pub struct Theme { /// Table column header text pub table_header_style: Style, pub table_text_style: Style, - pub table_alt_text_style: Style, + pub table_alt_style: Style, + pub table_disabled_style: Style, pub table_highlight_style: Style, pub table_title_style: Style, @@ -70,11 +71,15 @@ impl Default for Theme { .add_modifier(Modifier::BOLD) .add_modifier(Modifier::UNDERLINED), table_text_style: Style::default(), - table_alt_text_style: Style::default().bg(Color::DarkGray), + table_alt_style: Style::default().bg(Color::DarkGray), + table_disabled_style: Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::CROSSED_OUT), table_highlight_style: Style::default() .bg(PRIMARY_COLOR) .fg(Color::Black) - .add_modifier(Modifier::BOLD), + .add_modifier(Modifier::BOLD) + .add_modifier(Modifier::UNDERLINED), table_title_style: Style::default().add_modifier(Modifier::BOLD), template_preview_text: Style::default().fg(Color::Blue),