Skip to content

Commit

Permalink
refactor(term): add custom table widget to remove duplicate code
Browse files Browse the repository at this point in the history
There were duplicate sections in the process of generating the `ratatui` Table widget that was used for rendering the list of GH notifications, entries, and feeds.
To maintain the same UX, it was necessary to modify each of these individually when changing the table rendering process.
Therefore, We extracted a custom table widget and centralized the configuration process.
  • Loading branch information
ymgyt committed Aug 22, 2024
1 parent 55412ab commit 5a2953c
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 73 deletions.
33 changes: 11 additions & 22 deletions crates/synd_term/src/ui/components/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@ use crate::{
self,
components::{collections::FilterableVec, filter::FeedFilterer},
icon,
widgets::scrollbar::Scrollbar,
widgets::{scrollbar::Scrollbar, table::Table},
Context,
},
};
use ratatui::{
prelude::{Alignment, Buffer, Constraint, Layout, Rect},
style::Stylize,
text::{Line, Span, Text},
widgets::{
Block, BorderType, Borders, Cell, Padding, Paragraph, Row, StatefulWidget, Table,
TableState, Widget, Wrap,
},
widgets::{Block, BorderType, Borders, Cell, Padding, Paragraph, Row, Widget, Wrap},
};
use synd_feed::types::FeedUrl;

Expand Down Expand Up @@ -95,25 +92,17 @@ impl Entries {
fn render_entries(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
let entries_area = Block::new().padding(Padding::top(1)).inner(area);

let mut entries_state = TableState::new()
.with_offset(0)
.with_selected(self.entries.selected_index());

let (header, widths, rows) = self.entry_rows(cx);

let entries = Table::new(rows, widths)
.header(header.style(cx.theme.entries.header))
.column_spacing(2)
.highlight_symbol(ui::TABLE_HIGHLIGHT_SYMBOL)
.highlight_style(
cx.theme
.entries
.selected_entry
.add_modifier(cx.table_highlight_modifier()),
)
.highlight_spacing(ratatui::widgets::HighlightSpacing::Always);

StatefulWidget::render(entries, entries_area, buf, &mut entries_state);
Table::builder()
.header(header)
.widths(widths)
.rows(rows)
.theme(&cx.theme.entries)
.selected_idx(self.entries.selected_index())
.highlight_modifier(cx.table_highlight_modifier())
.build()
.render(entries_area, buf);

let header_rows = 2;
#[allow(clippy::cast_possible_truncation)]
Expand Down
34 changes: 12 additions & 22 deletions crates/synd_term/src/ui/components/gh_notifications/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use ratatui::{
layout::{Alignment, Constraint, Layout, Rect},
style::{Modifier, Style, Styled, Stylize},
text::{Line, Span},
widgets::{
Block, BorderType, Borders, Cell, Padding, Paragraph, Row, StatefulWidget, Table,
TableState, Widget, Wrap,
},
widgets::{Block, BorderType, Borders, Cell, Padding, Paragraph, Row, Widget, Wrap},
};
use serde::{Deserialize, Serialize};

Expand All @@ -29,14 +26,13 @@ use crate::{
TimeExt,
},
ui::{
self,
components::{
collections::FilterableVec,
filter::{CategoryFilterer, ComposedFilterer, MatcherFilterer},
},
extension::RectExt,
icon,
widgets::scrollbar::Scrollbar,
widgets::{scrollbar::Scrollbar, table::Table},
Context,
},
};
Expand Down Expand Up @@ -342,23 +338,17 @@ impl GhNotifications {
fn render_notifications(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
let notifications_area = Block::new().padding(Padding::top(1)).inner(area);

let mut notifications_state = TableState::new()
.with_offset(0)
.with_selected(self.notifications.selected_index());
let (header, widths, rows) = self.notification_rows(cx);
let notifications = Table::new(rows, widths)
.header(header.style(cx.theme.entries.header))
.column_spacing(2)
.highlight_symbol(ui::TABLE_HIGHLIGHT_SYMBOL)
.highlight_style(cx.theme.entries.selected_entry)
.highlight_spacing(ratatui::widgets::HighlightSpacing::Always);

StatefulWidget::render(
notifications,
notifications_area,
buf,
&mut notifications_state,
);

Table::builder()
.header(header)
.widths(widths)
.rows(rows)
.theme(&cx.theme.entries)
.selected_idx(self.notifications.selected_index())
.highlight_modifier(cx.table_highlight_modifier())
.build()
.render(notifications_area, buf);

let header_rows = 2;
#[allow(clippy::cast_possible_truncation)]
Expand Down
43 changes: 14 additions & 29 deletions crates/synd_term/src/ui/components/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use ratatui::{
style::{Modifier, Style, Stylize},
text::{Line, Span},
widgets::{
Block, BorderType, Borders, Cell, HighlightSpacing, Padding, Paragraph, Row,
StatefulWidget, Table, TableState, Tabs, Widget,
Block, BorderType, Borders, Cell, Padding, Paragraph, Row, Table as RatatuiTable, Tabs,
Widget,
},
};
use synd_feed::types::{FeedType, FeedUrl};
Expand All @@ -20,7 +20,7 @@ use crate::{
self,
components::{collections::FilterableVec, filter::FeedFilterer},
extension::RectExt,
widgets::scrollbar::Scrollbar,
widgets::{scrollbar::Scrollbar, table::Table},
Context,
},
};
Expand Down Expand Up @@ -153,32 +153,17 @@ impl Subscription {
fn render_feeds(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
let feeds_area = Block::new().padding(Padding::top(1)).inner(area);

let mut feeds_state = TableState::new()
.with_offset(0)
.with_selected(self.feeds.selected_index());

let (header, widths, rows) = self.feed_rows(cx);

let feeds = Table::new(rows, widths)
.block(Block::new().padding(Padding {
left: 0,
right: 0,
top: 0,
bottom: 0,
}))
.header(header.style(cx.theme.subscription.header))
.column_spacing(2)
.style(cx.theme.subscription.background)
.highlight_symbol(ui::TABLE_HIGHLIGHT_SYMBOL)
.highlight_style(
cx.theme
.subscription
.selected_feed
.add_modifier(cx.table_highlight_modifier()),
)
.highlight_spacing(HighlightSpacing::Always);

StatefulWidget::render(feeds, feeds_area, buf, &mut feeds_state);
Table::builder()
.header(header)
.widths(widths)
.rows(rows)
.theme(&cx.theme.entries)
.selected_idx(self.feeds.selected_index())
.highlight_modifier(cx.table_highlight_modifier())
.build()
.render(feeds_area, buf);

let header_rows = 2;
#[allow(clippy::cast_possible_truncation)]
Expand Down Expand Up @@ -353,7 +338,7 @@ impl Subscription {
]),
];

let table = Table::new(meta_rows, widths)
let table = RatatuiTable::new(meta_rows, widths)
.column_spacing(1)
.style(cx.theme.subscription.background);
Widget::render(table, meta_area, buf);
Expand Down Expand Up @@ -381,7 +366,7 @@ impl Subscription {
]);

let rows = feed.entries.iter().map(entry);
let table = Table::new(rows, widths)
let table = RatatuiTable::new(rows, widths)
.header(header.style(cx.theme.subscription.header))
.column_spacing(1)
.style(cx.theme.subscription.background);
Expand Down
1 change: 1 addition & 0 deletions crates/synd_term/src/ui/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod prompt;
pub(crate) mod scrollbar;
pub(crate) mod table;
pub(crate) mod throbber;
162 changes: 162 additions & 0 deletions crates/synd_term/src/ui/widgets/table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use ratatui::{
buffer::Buffer,
layout::{Constraint, Rect},
style::Modifier,
widgets::{Row, StatefulWidget, TableState},
};

use crate::ui::{self, theme::EntriesTheme};

pub(crate) struct TableBuilder<H, R, C, T, S, M> {
header: H,
rows: R,
widths: C,
theme: T,
selected_idx: S,
highlight_modifier: M,
}

impl Default for TableBuilder<(), (), (), (), (), ()> {
fn default() -> Self {
Self {
header: (),
rows: (),
widths: (),
theme: (),
selected_idx: (),
highlight_modifier: (),
}
}
}

impl<R, C, T, S, M> TableBuilder<(), R, C, T, S, M> {
pub(crate) fn header(self, header: Row<'_>) -> TableBuilder<Row<'_>, R, C, T, S, M> {
TableBuilder {
header,
rows: self.rows,
widths: self.widths,
theme: self.theme,
selected_idx: self.selected_idx,
highlight_modifier: self.highlight_modifier,
}
}
}

impl<H, C, T, S, M> TableBuilder<H, (), C, T, S, M> {
pub(crate) fn rows<'a, Rows>(self, rows: Rows) -> TableBuilder<H, Vec<Row<'a>>, C, T, S, M>
where
Rows: IntoIterator,
Rows::Item: Into<Row<'a>>,
{
TableBuilder {
header: self.header,
rows: rows.into_iter().map(Into::into).collect(),
widths: self.widths,
theme: self.theme,
selected_idx: self.selected_idx,
highlight_modifier: self.highlight_modifier,
}
}
}

impl<H, R, T, S, M> TableBuilder<H, R, (), T, S, M> {
pub(crate) fn widths<C>(self, widths: C) -> TableBuilder<H, R, Vec<Constraint>, T, S, M>
where
C: IntoIterator,
C::Item: Into<Constraint>,
{
TableBuilder {
header: self.header,
rows: self.rows,
widths: widths.into_iter().map(Into::into).collect(),
theme: self.theme,
selected_idx: self.selected_idx,
highlight_modifier: self.highlight_modifier,
}
}
}

impl<H, R, C, S, M> TableBuilder<H, R, C, (), S, M> {
pub(crate) fn theme(self, theme: &EntriesTheme) -> TableBuilder<H, R, C, &EntriesTheme, S, M>
where
C: IntoIterator,
C::Item: Into<Constraint>,
{
TableBuilder {
header: self.header,
rows: self.rows,
widths: self.widths,
theme,
selected_idx: self.selected_idx,
highlight_modifier: self.highlight_modifier,
}
}
}

impl<H, R, C, T, M> TableBuilder<H, R, C, T, (), M> {
pub(crate) fn selected_idx(self, selected_idx: usize) -> TableBuilder<H, R, C, T, usize, M> {
TableBuilder {
header: self.header,
rows: self.rows,
widths: self.widths,
theme: self.theme,
selected_idx,
highlight_modifier: self.highlight_modifier,
}
}
}

impl<H, R, C, T, S> TableBuilder<H, R, C, T, S, ()> {
pub(crate) fn highlight_modifier(
self,
highlight_modifier: Modifier,
) -> TableBuilder<H, R, C, T, S, Modifier> {
TableBuilder {
header: self.header,
rows: self.rows,
widths: self.widths,
theme: self.theme,
selected_idx: self.selected_idx,
highlight_modifier,
}
}
}

impl<'a> TableBuilder<Row<'a>, Vec<Row<'a>>, Vec<Constraint>, &'a EntriesTheme, usize, Modifier> {
pub(crate) fn build(self) -> Table<'a> {
let TableBuilder {
header,
rows,
widths,
theme,
selected_idx,
highlight_modifier,
} = self;

let table = ratatui::widgets::Table::new(rows, widths)
.header(header.style(theme.header))
.column_spacing(2)
.highlight_symbol(ui::TABLE_HIGHLIGHT_SYMBOL)
.highlight_style(theme.selected_entry.add_modifier(highlight_modifier))
.highlight_spacing(ratatui::widgets::HighlightSpacing::Always);

let state = TableState::new().with_offset(0).with_selected(selected_idx);

Table { table, state }
}
}

pub(crate) struct Table<'a> {
table: ratatui::widgets::Table<'a>,
state: TableState,
}

impl<'a> Table<'a> {
pub(crate) fn builder() -> TableBuilder<(), (), (), (), (), ()> {
TableBuilder::default()
}

pub(crate) fn render(mut self, area: Rect, buf: &mut Buffer) {
StatefulWidget::render(self.table, area, buf, &mut self.state);
}
}

0 comments on commit 5a2953c

Please sign in to comment.