refactor: per-row styling, remove seemingly redundant table code

This commit is contained in:
ClementTsang 2022-05-03 04:46:43 -04:00
parent 2e51590bf5
commit df1a418327
6 changed files with 75 additions and 52 deletions

View File

@ -7,6 +7,7 @@ use tui::widgets::TableState;
use crate::{ use crate::{
app::{layout_manager::BottomWidgetType, query::*}, app::{layout_manager::BottomWidgetType, query::*},
constants, constants,
data_conversion::CellContent,
data_harvester::processes::{self, ProcessSorting}, data_harvester::processes::{self, ProcessSorting},
}; };
use ProcessSorting::*; use ProcessSorting::*;
@ -70,10 +71,7 @@ impl WidthBounds {
pub struct TableComponentColumn { pub struct TableComponentColumn {
/// The name of the column. Displayed if possible as the header. /// The name of the column. Displayed if possible as the header.
pub name: Cow<'static, str>, pub name: CellContent,
/// An optional alternative column name. Displayed if `name` doesn't fit.
pub alt: Option<Cow<'static, str>>,
/// A restriction on this column's width, if desired. /// A restriction on this column's width, if desired.
pub width_bounds: WidthBounds, pub width_bounds: WidthBounds,
@ -85,8 +83,14 @@ impl TableComponentColumn {
I: Into<Cow<'static, str>>, I: Into<Cow<'static, str>>,
{ {
Self { Self {
name: name.into(), name: if let Some(alt) = alt {
alt: alt.map(Into::into), CellContent::HasAlt {
alt: alt.into(),
main: name.into(),
}
} else {
CellContent::Simple(name.into())
},
width_bounds, width_bounds,
} }
} }

View File

@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
app::{self, TableComponentState}, app::{self, TableComponentState},
constants::{SIDE_BORDERS, TABLE_GAP_HEIGHT_LIMIT}, constants::{SIDE_BORDERS, TABLE_GAP_HEIGHT_LIMIT},
data_conversion::{CellContent, TableData}, data_conversion::{CellContent, TableData, TableRow},
}; };
pub struct TextTable<'a> { pub struct TextTable<'a> {
@ -98,14 +98,12 @@ impl<'a> TextTable<'a> {
pub fn draw_text_table<B: Backend>( pub fn draw_text_table<B: Backend>(
&self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState, &self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState,
table_data: &TableData, table_data: &TableData,
) -> Rect { ) {
// TODO: This is a *really* ugly hack to get basic mode to hide the border when not selected, without shifting everything.
let is_not_basic = self.is_on_widget || self.draw_border;
let margined_draw_loc = Layout::default() let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)]) .constraints([Constraint::Percentage(100)])
.horizontal_margin(if self.is_on_widget || self.draw_border { .horizontal_margin(if is_not_basic { 0 } else { 1 })
0
} else {
1
})
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.split(draw_loc)[0]; .split(draw_loc)[0];
@ -135,7 +133,6 @@ impl<'a> TextTable<'a> {
if inner_width == 0 || inner_height == 0 { if inner_width == 0 || inner_height == 0 {
f.render_widget(disk_block, margined_draw_loc); f.render_widget(disk_block, margined_draw_loc);
margined_draw_loc
} else { } else {
let show_header = inner_height > 1; let show_header = inner_height > 1;
let header_height = if show_header { 1 } else { 0 }; let header_height = if show_header { 1 } else { 0 };
@ -183,15 +180,24 @@ impl<'a> TextTable<'a> {
let columns = &state.columns; let columns = &state.columns;
let widths = &state.calculated_widths; let widths = &state.calculated_widths;
// TODO: Maybe truncate this too? let header = Row::new(
let header = Row::new(columns.iter().map(|c| Text::raw(c.name.as_ref()))) columns
.style(self.header_style) .iter()
.bottom_margin(table_gap); .zip(widths)
.map(|(c, width)| truncate_text(&c.name, (*width).into(), None)),
)
.style(self.header_style)
.bottom_margin(table_gap);
let disk_rows = sliced_vec.iter().map(|row| { let disk_rows = sliced_vec.iter().map(|row| {
let (row, style) = match row {
TableRow::Raw(row) => (row, None),
TableRow::Styled(row, style) => (row, Some(*style)),
};
Row::new( Row::new(
row.iter() row.iter()
.zip(widths) .zip(widths)
.map(|(cell, width)| truncate_text(cell, (*width).into())), .map(|(cell, width)| truncate_text(cell, (*width).into(), style)),
) )
}); });
@ -218,21 +224,22 @@ impl<'a> TextTable<'a> {
margined_draw_loc, margined_draw_loc,
&mut state.table_state, &mut state.table_state,
); );
margined_draw_loc
} }
} }
} }
/// Truncates text if it is too long, and adds an ellipsis at the end if needed. /// Truncates text if it is too long, and adds an ellipsis at the end if needed.
fn truncate_text(content: &CellContent, width: usize) -> Text<'_> { fn truncate_text(content: &CellContent, width: usize, row_style: Option<Style>) -> Text<'_> {
let (text, opt) = match content { let (text, opt) = match content {
CellContent::Simple(s) => (s, None), CellContent::Simple(s) => (s, None),
CellContent::HasShort { short, long } => (long, Some(short)), CellContent::HasAlt {
alt: short,
main: long,
} => (long, Some(short)),
}; };
let graphemes = UnicodeSegmentation::graphemes(text.as_ref(), true).collect::<Vec<&str>>(); let graphemes = UnicodeSegmentation::graphemes(text.as_ref(), true).collect::<Vec<&str>>();
if graphemes.len() > width && width > 0 { let mut text = if graphemes.len() > width && width > 0 {
if let Some(s) = opt { if let Some(s) = opt {
// If an alternative exists, use that. // If an alternative exists, use that.
Text::raw(s.as_ref()) Text::raw(s.as_ref())
@ -243,7 +250,13 @@ fn truncate_text(content: &CellContent, width: usize) -> Text<'_> {
} }
} else { } else {
Text::raw(text.as_ref()) Text::raw(text.as_ref())
};
if let Some(row_style) = row_style {
text.patch_style(row_style);
} }
text
} }
/// Gets the starting position of a table. /// Gets the starting position of a table.

View File

@ -183,7 +183,7 @@ impl Painter {
); );
let points = self.generate_points( let points = self.generate_points(
&cpu_widget_state, cpu_widget_state,
cpu_data, cpu_data,
app_state.app_config_fields.show_average_cpu, app_state.app_config_fields.show_average_cpu,
); );
@ -224,7 +224,7 @@ impl Painter {
let recalculate_column_widths = app_state.should_get_widget_bounds(); let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1)) if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1))
{ {
cpu_widget_state.is_legend_hidden = false; cpu_widget_state.is_legend_hidden = false; // TODO: This line (and the one above, see caller) is pretty dumb.
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data; let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let cpu_table_state = &mut cpu_widget_state.scroll_state.table_state; let cpu_table_state = &mut cpu_widget_state.scroll_state.table_state;
let is_on_widget = widget_id == app_state.current_widget.widget_id; let is_on_widget = widget_id == app_state.current_widget.widget_id;

View File

@ -22,7 +22,7 @@ impl Painter {
} else { } else {
(self.colours.border_style, self.colours.text_style) (self.colours.border_style, self.colours.text_style)
}; };
let margined_draw_loc = TextTable { TextTable {
table_gap: app_state.app_config_fields.table_gap, table_gap: app_state.app_config_fields.table_gap,
is_force_redraw: app_state.is_force_redraw, is_force_redraw: app_state.is_force_redraw,
recalculate_column_widths, recalculate_column_widths,
@ -48,11 +48,9 @@ impl Painter {
if app_state.should_get_widget_bounds() { if app_state.should_get_widget_bounds() {
// Update draw loc in widget map // Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) { if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y)); widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner = Some(( widget.bottom_right_corner =
margined_draw_loc.x + margined_draw_loc.width, Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
margined_draw_loc.y + margined_draw_loc.height,
));
} }
} }
} }

View File

@ -22,7 +22,7 @@ impl Painter {
} else { } else {
(self.colours.border_style, self.colours.text_style) (self.colours.border_style, self.colours.text_style)
}; };
let margined_draw_loc = TextTable { TextTable {
table_gap: app_state.app_config_fields.table_gap, table_gap: app_state.app_config_fields.table_gap,
is_force_redraw: app_state.is_force_redraw, is_force_redraw: app_state.is_force_redraw,
recalculate_column_widths, recalculate_column_widths,
@ -47,13 +47,10 @@ impl Painter {
if app_state.should_get_widget_bounds() { if app_state.should_get_widget_bounds() {
// Update draw loc in widget map // Update draw loc in widget map
// Note there is no difference between this and using draw_loc, but I'm too lazy to fix it.
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) { if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y)); widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner = Some(( widget.bottom_right_corner =
margined_draw_loc.x + margined_draw_loc.width, Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
margined_draw_loc.y + margined_draw_loc.height,
));
} }
} }
} }

View File

@ -25,27 +25,38 @@ pub struct ConvertedBatteryData {
pub enum CellContent { pub enum CellContent {
Simple(Cow<'static, str>), Simple(Cow<'static, str>),
HasShort { HasAlt {
short: Cow<'static, str>, alt: Cow<'static, str>,
long: Cow<'static, str>, main: Cow<'static, str>,
}, },
} }
impl CellContent { impl CellContent {
/// Returns the length of the [`CellContent`]. Note that for a [`CellContent::HasAlt`], it will return
/// the length of the "main" field.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
match self { match self {
CellContent::Simple(s) => s.len(), CellContent::Simple(s) => s.len(),
CellContent::HasShort { short: _, long } => long.len(), CellContent::HasAlt { alt: _, main: long } => long.len(),
} }
} }
pub fn is_empty(&self) -> bool {
self.len() == 0
}
} }
#[derive(Default)] #[derive(Default)]
pub struct TableData { pub struct TableData {
pub data: Vec<Vec<CellContent>>, pub data: Vec<TableRow>,
pub row_widths: Vec<usize>, pub row_widths: Vec<usize>,
} }
pub enum TableRow {
Raw(Vec<CellContent>),
Styled(Vec<CellContent>, tui::style::Style),
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct ConvertedNetworkData { pub struct ConvertedNetworkData {
pub rx: Vec<Point>, pub rx: Vec<Point>,
@ -111,7 +122,7 @@ pub fn convert_temp_row(app: &App) -> TableData {
let temp_type = &app.app_config_fields.temperature_type; let temp_type = &app.app_config_fields.temperature_type;
let mut row_widths = vec![0; 2]; let mut row_widths = vec![0; 2];
let mut sensor_vector: Vec<Vec<CellContent>> = current_data let mut sensor_vector: Vec<TableRow> = current_data
.temp_harvest .temp_harvest
.iter() .iter()
.map(|temp_harvest| { .map(|temp_harvest| {
@ -134,15 +145,15 @@ pub fn convert_temp_row(app: &App) -> TableData {
*curr = std::cmp::max(*curr, r.len()); *curr = std::cmp::max(*curr, r.len());
}); });
row TableRow::Raw(row)
}) })
.collect(); .collect();
if sensor_vector.is_empty() { if sensor_vector.is_empty() {
sensor_vector.push(vec![ sensor_vector.push(TableRow::Raw(vec![
CellContent::Simple("No Sensors Found".into()), CellContent::Simple("No Sensors Found".into()),
CellContent::Simple("".into()), CellContent::Simple("".into()),
]); ]));
} }
TableData { TableData {
@ -152,7 +163,7 @@ pub fn convert_temp_row(app: &App) -> TableData {
} }
pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> TableData { pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> TableData {
let mut disk_vector: Vec<Vec<CellContent>> = Vec::new(); let mut disk_vector: Vec<TableRow> = Vec::new();
let mut row_widths = vec![0; 8]; let mut row_widths = vec![0; 8];
current_data current_data
@ -197,14 +208,14 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> TableData
row_widths.iter_mut().zip(&row).for_each(|(curr, r)| { row_widths.iter_mut().zip(&row).for_each(|(curr, r)| {
*curr = std::cmp::max(*curr, r.len()); *curr = std::cmp::max(*curr, r.len());
}); });
disk_vector.push(row); disk_vector.push(TableRow::Raw(row));
}); });
if disk_vector.is_empty() { if disk_vector.is_empty() {
disk_vector.push(vec![ disk_vector.push(TableRow::Raw(vec![
CellContent::Simple("No Disks Found".into()), CellContent::Simple("No Disks Found".into()),
CellContent::Simple("".into()), CellContent::Simple("".into()),
]); ]));
} }
TableData { TableData {