mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-23 13:45:12 +02:00
refactor: per-row styling, remove seemingly redundant table code
This commit is contained in:
parent
2e51590bf5
commit
df1a418327
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user