Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve style of toggled rows #96

Merged
merged 1 commit into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased] - 2024-01-14

### Changed

- Improved styling of toggled table rows

## [0.12.0] - 2024-01-07

### Added
Expand Down
105 changes: 95 additions & 10 deletions src/tui/view/common/table.rs
Original file line number Diff line number Diff line change
@@ -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<R>,
/// Optional header row. Length should match column length
pub header: Option<[&'a str; COLS]>,
/// Use a different styling for alternating rows
Expand All @@ -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<Cell<'a>>,
Rows: IntoIterator<Item = [Cll; COLS]>,
{
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);
Expand All @@ -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<Text<'a>>,
{
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
})
}
}
2 changes: 1 addition & 1 deletion src/tui/view/component/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
28 changes: 13 additions & 15 deletions src/tui/view/component/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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};
Expand Down Expand Up @@ -395,20 +397,17 @@ impl RowState {
fn to_table<'a>(
state: &'a SelectState<Dynamic, RowState, TableState>,
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),
Expand All @@ -417,7 +416,6 @@ fn to_table<'a>(
Constraint::Percentage(50),
Constraint::Min(3),
],
alternate_row_style: true,
..Default::default()
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/tui/view/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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),
Expand Down
Loading