lib: Rework table header

This commit is contained in:
Erik Kundt
2024-04-10 13:33:15 +02:00
parent 2ee9528cc3
commit 62ffd3de9c
2 changed files with 73 additions and 24 deletions
+26 -14
View File
@@ -12,6 +12,8 @@ use termion::event::Key;
use ratatui::prelude::*;
use ratatui::widgets::{Block, BorderType, Borders, Cell, Row, TableState};
use self::container::Header;
use super::theme::style;
use super::{layout, span};
@@ -145,7 +147,6 @@ pub struct TableProps<R: ToRow> {
pub items: Vec<R>,
pub focus: bool,
pub columns: Vec<Column>,
pub has_header: bool,
pub has_footer: bool,
pub cutoff: usize,
pub cutoff_after: usize,
@@ -158,7 +159,6 @@ impl<R: ToRow> Default for TableProps<R> {
items: vec![],
focus: false,
columns: vec![],
has_header: false,
has_footer: false,
cutoff: usize::MAX,
cutoff_after: usize::MAX,
@@ -174,6 +174,8 @@ pub struct Table<A, R: ToRow> {
pub props: TableProps<R>,
/// Internal selection state
state: TableState,
/// Table header widget
header: Option<Header<A>>,
}
impl<A, R: ToRow> Table<A, R> {
@@ -187,11 +189,6 @@ impl<A, R: ToRow> Table<A, R> {
self
}
pub fn header(mut self, has_header: bool) -> Self {
self.props.has_header = has_header;
self
}
pub fn footer(mut self, has_footer: bool) -> Self {
self.props.has_footer = has_footer;
self
@@ -208,6 +205,11 @@ impl<A, R: ToRow> Table<A, R> {
self
}
pub fn header(mut self, header: Header<A>) -> Self {
self.header = Some(header);
self
}
fn prev(&mut self) -> Option<usize> {
let selected = self.selected().map(|current| current.saturating_sub(1));
self.state.select(selected);
@@ -289,11 +291,11 @@ impl<A, R: ToRow> Table<A, R> {
}
}
impl<S, A, R> Widget<S, A> for Table<A, R>
impl<A, R> Widget<(), A> for Table<A, R>
where
R: ToRow,
{
fn new(state: &S, action_tx: UnboundedSender<A>) -> Self
fn new(state: &(), action_tx: UnboundedSender<A>) -> Self
where
Self: Sized,
{
@@ -301,11 +303,12 @@ where
action_tx: action_tx.clone(),
props: TableProps::default(),
state: TableState::default().with_selected(Some(0)),
header: None,
}
.move_with_state(state)
}
fn move_with_state(self, _state: &S) -> Self
fn move_with_state(self, _state: &()) -> Self
where
Self: Sized,
{
@@ -350,6 +353,9 @@ where
R: ToRow + Debug,
{
fn render<B: Backend>(&self, frame: &mut ratatui::Frame, area: Rect, _props: ()) {
let header_height = if self.header.is_some() { 3 } else { 0 };
let [header_area, table_area] =
Layout::vertical([Constraint::Length(header_height), Constraint::Min(1)]).areas(area);
let widths: Vec<Constraint> = self
.props
.columns
@@ -366,7 +372,7 @@ where
widths.iter().collect::<Vec<_>>()
};
let borders = match (self.props.has_header, self.props.has_footer) {
let borders = match (self.header.is_some(), self.props.has_footer) {
(false, false) => Borders::ALL,
(true, false) => Borders::BOTTOM | Borders::LEFT | Borders::RIGHT,
(false, true) => Borders::TOP | Borders::LEFT | Borders::RIGHT,
@@ -407,16 +413,22 @@ where
)
.highlight_style(style::highlight());
frame.render_stateful_widget(rows, area, &mut self.state.clone());
if let Some(header) = &self.header {
header.render::<B>(frame, header_area, ());
}
frame.render_stateful_widget(rows, table_area, &mut self.state.clone());
} else {
let block = Block::default()
.border_style(style::border(self.props.focus))
.border_type(BorderType::Rounded)
.borders(borders);
frame.render_widget(block, area);
if let Some(header) = &self.header {
header.render::<B>(frame, header_area, ());
}
frame.render_widget(block, table_area);
let center = layout::centered_rect(area, 50, 10);
let center = layout::centered_rect(table_area, 50, 10);
let hint = Text::from(span::default("Nothing to show".to_string()))
.centered()
.light_magenta()
+47 -10
View File
@@ -108,23 +108,55 @@ pub struct HeaderProps {
pub focus: bool,
}
impl Default for HeaderProps {
fn default() -> Self {
Self {
columns: vec![],
cutoff: usize::MAX,
cutoff_after: usize::MAX,
focus: false,
}
}
}
pub struct Header<A> {
/// Sending actions to the state store
pub action_tx: UnboundedSender<A>,
/// Internal props
props: HeaderProps,
}
impl<S, A> Widget<S, A> for Header<A> {
fn new(state: &S, action_tx: UnboundedSender<A>) -> Self
impl<A> Header<A> {
pub fn columns(mut self, columns: Vec<Column>) -> Self {
self.props.columns = columns;
self
}
pub fn focus(mut self, focus: bool) -> Self {
self.props.focus = focus;
self
}
pub fn cutoff(mut self, cutoff: usize, cutoff_after: usize) -> Self {
self.props.cutoff = cutoff;
self.props.cutoff_after = cutoff_after;
self
}
}
impl<A> Widget<(), A> for Header<A> {
fn new(state: &(), action_tx: UnboundedSender<A>) -> Self
where
Self: Sized,
{
Self {
action_tx: action_tx.clone(),
props: HeaderProps::default(),
}
.move_with_state(state)
}
fn move_with_state(self, _state: &S) -> Self
fn move_with_state(self, _state: &()) -> Self
where
Self: Sized,
{
@@ -134,9 +166,10 @@ impl<S, A> Widget<S, A> for Header<A> {
fn handle_key_event(&mut self, _key: Key) {}
}
impl<A> Render<HeaderProps> for Header<A> {
fn render<B: Backend>(&self, frame: &mut ratatui::Frame, area: Rect, props: HeaderProps) {
let widths: Vec<Constraint> = props
impl<A> Render<()> for Header<A> {
fn render<B: Backend>(&self, frame: &mut ratatui::Frame, area: Rect, _props: ()) {
let widths: Vec<Constraint> = self
.props
.columns
.iter()
.filter_map(|column| {
@@ -147,7 +180,8 @@ impl<A> Render<HeaderProps> for Header<A> {
}
})
.collect();
let cells = props
let cells = self
.props
.columns
.iter()
.filter_map(|column| {
@@ -159,8 +193,11 @@ impl<A> Render<HeaderProps> for Header<A> {
})
.collect::<Vec<_>>();
let widths = if area.width < props.cutoff as u16 {
widths.iter().take(props.cutoff_after).collect::<Vec<_>>()
let widths = if area.width < self.props.cutoff as u16 {
widths
.iter()
.take(self.props.cutoff_after)
.collect::<Vec<_>>()
} else {
widths.iter().collect::<Vec<_>>()
};
@@ -168,7 +205,7 @@ impl<A> Render<HeaderProps> for Header<A> {
// Render header
let block = HeaderBlock::default()
.borders(Borders::ALL)
.border_style(style::border(props.focus))
.border_style(style::border(self.props.focus))
.border_type(BorderType::Rounded);
let header_layout = Layout::default()