refactor: don't draw header if too short

This commit is contained in:
ClementTsang 2022-05-02 18:27:09 -04:00
parent c296b8bf5a
commit 2e51590bf5
5 changed files with 207 additions and 188 deletions

View File

@ -117,9 +117,7 @@ impl TableComponentState {
/// Calculates widths for the columns for this table. /// Calculates widths for the columns for this table.
/// ///
/// * `total_width` is the, well, total width available. **NOTE:** This function automatically /// * `total_width` is the, well, total width available.
/// takes away 2 from the width as part of the left/right
/// bounds.
/// * `left_to_right` is a boolean whether to go from left to right if true, or right to left if /// * `left_to_right` is a boolean whether to go from left to right if true, or right to left if
/// false. /// false.
/// ///
@ -128,71 +126,68 @@ impl TableComponentState {
use itertools::Either; use itertools::Either;
use std::cmp::{max, min}; use std::cmp::{max, min};
if total_width > 2 { let mut total_width_left = total_width;
let initial_width = total_width - 2;
let mut total_width_left = initial_width;
let column_widths = &mut self.calculated_widths; let column_widths = &mut self.calculated_widths;
*column_widths = vec![0; self.columns.len()]; *column_widths = vec![0; self.columns.len()];
let columns = if left_to_right { let columns = if left_to_right {
Either::Left(self.columns.iter().enumerate()) Either::Left(self.columns.iter().enumerate())
} else { } else {
Either::Right(self.columns.iter().enumerate().rev()) Either::Right(self.columns.iter().enumerate().rev())
}; };
for (itx, column) in columns { for (itx, column) in columns {
match &column.width_bounds { match &column.width_bounds {
WidthBounds::Soft { WidthBounds::Soft {
min_width, min_width,
desired, desired,
max_percentage, max_percentage,
} => { } => {
let soft_limit = max( let soft_limit = max(
if let Some(max_percentage) = max_percentage { if let Some(max_percentage) = max_percentage {
// Rust doesn't have an `into()` or `try_into()` for floats to integers??? // Rust doesn't have an `into()` or `try_into()` for floats to integers???
((*max_percentage * f32::from(initial_width)).ceil()) as u16 ((*max_percentage * f32::from(total_width)).ceil()) as u16
} else {
*desired
},
*min_width,
);
let space_taken = min(min(soft_limit, *desired), total_width_left);
if *min_width > space_taken {
break;
} else { } else {
total_width_left = total_width_left.saturating_sub(space_taken + 1); *desired
column_widths[itx] = space_taken; },
} *min_width,
);
let space_taken = min(min(soft_limit, *desired), total_width_left);
if *min_width > space_taken {
break;
} else {
total_width_left = total_width_left.saturating_sub(space_taken + 1);
column_widths[itx] = space_taken;
} }
WidthBounds::Hard(width) => { }
let space_taken = min(*width, total_width_left); WidthBounds::Hard(width) => {
let space_taken = min(*width, total_width_left);
if *width > space_taken { if *width > space_taken {
break; break;
} else { } else {
total_width_left = total_width_left.saturating_sub(space_taken + 1); total_width_left = total_width_left.saturating_sub(space_taken + 1);
column_widths[itx] = space_taken; column_widths[itx] = space_taken;
}
} }
} }
} }
}
while let Some(0) = column_widths.last() { while let Some(0) = column_widths.last() {
column_widths.pop(); column_widths.pop();
} }
if !column_widths.is_empty() { if !column_widths.is_empty() {
// Redistribute remaining. // Redistribute remaining.
let amount_per_slot = total_width_left / column_widths.len() as u16; let amount_per_slot = total_width_left / column_widths.len() as u16;
total_width_left %= column_widths.len() as u16; total_width_left %= column_widths.len() as u16;
for (index, width) in column_widths.iter_mut().enumerate() { for (index, width) in column_widths.iter_mut().enumerate() {
if index < total_width_left.into() { if index < total_width_left.into() {
*width += amount_per_slot + 1; *width += amount_per_slot + 1;
} else { } else {
*width += amount_per_slot; *width += amount_per_slot;
}
} }
} }
} }

View File

@ -19,7 +19,6 @@ use crate::{
pub struct TextTable<'a> { pub struct TextTable<'a> {
pub table_gap: u16, pub table_gap: u16,
pub table_height_offset: u16,
pub is_force_redraw: bool, pub is_force_redraw: bool,
pub recalculate_column_widths: bool, pub recalculate_column_widths: bool,
@ -100,71 +99,23 @@ impl<'a> TextTable<'a> {
&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 { ) -> Rect {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT { let margined_draw_loc = Layout::default()
0 .constraints([Constraint::Percentage(100)])
} else { .horizontal_margin(if self.is_on_widget || self.draw_border {
self.table_gap 0
}; } else {
1
let sliced_vec = { })
let num_rows = usize::from( .direction(Direction::Horizontal)
(draw_loc.height + 1 - table_gap).saturating_sub(self.table_height_offset), .split(draw_loc)[0];
);
let start = get_start_position(
num_rows,
&state.scroll_direction,
&mut state.scroll_bar,
state.current_scroll_position,
self.is_force_redraw,
);
let end = min(table_data.data.len(), start + num_rows + 1);
state
.table_state
.select(Some(state.current_scroll_position.saturating_sub(start)));
&table_data.data[start..end]
};
// Calculate widths
if self.recalculate_column_widths {
state
.columns
.iter_mut()
.zip(&table_data.row_widths)
.for_each(|(column, data_width)| match &mut column.width_bounds {
app::WidthBounds::Soft {
min_width: _,
desired,
max_percentage: _,
} => {
*desired = std::cmp::max(column.name.len(), *data_width) as u16;
}
app::WidthBounds::Hard(_width) => {}
});
state.calculate_column_widths(draw_loc.width, self.left_to_right);
}
let columns = &state.columns;
let widths = &state.calculated_widths;
// TODO: Maybe truncate this too?
let header = Row::new(columns.iter().map(|c| Text::raw(c.name.as_ref())))
.style(self.header_style)
.bottom_margin(table_gap);
let disk_rows = sliced_vec.iter().map(|row| {
Row::new(
row.iter()
.zip(widths)
.map(|(cell, width)| truncate_text(cell, (*width).into())),
)
});
let title = self.generate_title(
draw_loc,
state.current_scroll_position.saturating_add(1),
table_data.data.len(),
);
let disk_block = if self.draw_border { let disk_block = if self.draw_border {
let title = self.generate_title(
draw_loc,
state.current_scroll_position.saturating_add(1),
table_data.data.len(),
);
Block::default() Block::default()
.title(title) .title(title)
.borders(Borders::ALL) .borders(Borders::ALL)
@ -177,34 +128,99 @@ impl<'a> TextTable<'a> {
Block::default().borders(Borders::NONE) Block::default().borders(Borders::NONE)
}; };
let margined_draw_loc = Layout::default() let (inner_width, inner_height) = {
.constraints([Constraint::Percentage(100)]) let inner = disk_block.inner(margined_draw_loc);
.horizontal_margin(if self.is_on_widget || self.draw_border { (inner.width, inner.height)
};
if inner_width == 0 || inner_height == 0 {
f.render_widget(disk_block, margined_draw_loc);
margined_draw_loc
} else {
let show_header = inner_height > 1;
let header_height = if show_header { 1 } else { 0 };
let table_gap = if !show_header || draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0 0
} else { } else {
1 self.table_gap
}) };
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw! let sliced_vec = {
f.render_stateful_widget( let num_rows = usize::from(inner_height.saturating_sub(table_gap + header_height));
Table::new(disk_rows) let start = get_start_position(
.block(disk_block) num_rows,
.header(header) &state.scroll_direction,
.highlight_style(self.highlighted_text_style) &mut state.scroll_bar,
.style(self.text_style) state.current_scroll_position,
.widths( self.is_force_redraw,
);
let end = min(table_data.data.len(), start + num_rows + 1);
state
.table_state
.select(Some(state.current_scroll_position.saturating_sub(start)));
&table_data.data[start..end]
};
// Calculate widths
if self.recalculate_column_widths {
state
.columns
.iter_mut()
.zip(&table_data.row_widths)
.for_each(|(column, data_width)| match &mut column.width_bounds {
app::WidthBounds::Soft {
min_width: _,
desired,
max_percentage: _,
} => {
*desired = std::cmp::max(column.name.len(), *data_width) as u16;
}
app::WidthBounds::Hard(_width) => {}
});
state.calculate_column_widths(inner_width, self.left_to_right);
}
let columns = &state.columns;
let widths = &state.calculated_widths;
// TODO: Maybe truncate this too?
let header = Row::new(columns.iter().map(|c| Text::raw(c.name.as_ref())))
.style(self.header_style)
.bottom_margin(table_gap);
let disk_rows = sliced_vec.iter().map(|row| {
Row::new(
row.iter()
.zip(widths)
.map(|(cell, width)| truncate_text(cell, (*width).into())),
)
});
let widget = {
let mut table = Table::new(disk_rows)
.block(disk_block)
.highlight_style(self.highlighted_text_style)
.style(self.text_style);
if show_header {
table = table.header(header);
}
table
};
f.render_stateful_widget(
widget.widths(
&(widths &(widths
.iter() .iter()
.map(|w| Constraint::Length(*w)) .map(|w| Constraint::Length(*w))
.collect::<Vec<_>>()), .collect::<Vec<_>>()),
), ),
margined_draw_loc, margined_draw_loc,
&mut state.table_state, &mut state.table_state,
); );
margined_draw_loc margined_draw_loc
}
} }
} }

View File

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use crate::{ use crate::{
app::{layout_manager::WidgetDirection, App}, app::{layout_manager::WidgetDirection, App, CpuWidgetState},
canvas::{ canvas::{
components::{GraphData, TimeGraph}, components::{GraphData, TimeGraph},
drawing_utils::{get_column_widths, get_start_position, should_hide_x_label}, drawing_utils::{get_column_widths, get_start_position, should_hide_x_label},
@ -115,6 +115,56 @@ impl Painter {
} }
} }
fn generate_points<'a>(
&self, cpu_widget_state: &CpuWidgetState, cpu_data: &'a [ConvertedCpuData],
show_avg_cpu: bool,
) -> Vec<GraphData<'a>> {
let show_avg_offset = if show_avg_cpu { AVG_POSITION } else { 0 };
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
if current_scroll_position == ALL_POSITION {
// This case ensures the other cases cannot have the position be equal to 0.
cpu_data
.iter()
.enumerate()
.rev()
.map(|(itx, cpu)| {
let style = if show_avg_cpu && itx == AVG_POSITION {
self.colours.avg_colour_style
} else if itx == ALL_POSITION {
self.colours.all_colour_style
} else {
let offset_position = itx - 1; // Because of the all position
self.colours.cpu_colour_styles[(offset_position - show_avg_offset)
% self.colours.cpu_colour_styles.len()]
};
GraphData {
points: &cpu.cpu_data[..],
style,
name: None,
}
})
.collect::<Vec<_>>()
} else if let Some(cpu) = cpu_data.get(current_scroll_position) {
let style = if show_avg_cpu && current_scroll_position == AVG_POSITION {
self.colours.avg_colour_style
} else {
let offset_position = current_scroll_position - 1; // Because of the all position
self.colours.cpu_colour_styles
[(offset_position - show_avg_offset) % self.colours.cpu_colour_styles.len()]
};
vec![GraphData {
points: &cpu.cpu_data[..],
style,
name: None,
}]
} else {
vec![]
}
}
fn draw_cpu_graph<B: Backend>( fn draw_cpu_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) { ) {
@ -131,52 +181,12 @@ impl Painter {
&mut cpu_widget_state.autohide_timer, &mut cpu_widget_state.autohide_timer,
draw_loc, draw_loc,
); );
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let show_avg_offset = if show_avg_cpu { AVG_POSITION } else { 0 };
let points = {
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
if current_scroll_position == ALL_POSITION {
// This case ensures the other cases cannot have the position be equal to 0.
cpu_data
.iter()
.enumerate()
.rev()
.map(|(itx, cpu)| {
let style = if show_avg_cpu && itx == AVG_POSITION {
self.colours.avg_colour_style
} else if itx == ALL_POSITION {
self.colours.all_colour_style
} else {
let offset_position = itx - 1; // Because of the all position
self.colours.cpu_colour_styles[(offset_position - show_avg_offset)
% self.colours.cpu_colour_styles.len()]
};
GraphData { let points = self.generate_points(
points: &cpu.cpu_data[..], &cpu_widget_state,
style, cpu_data,
name: None, app_state.app_config_fields.show_average_cpu,
} );
})
.collect::<Vec<_>>()
} else if let Some(cpu) = cpu_data.get(current_scroll_position) {
let style = if show_avg_cpu && current_scroll_position == AVG_POSITION {
self.colours.avg_colour_style
} else {
let offset_position = current_scroll_position - 1; // Because of the all position
self.colours.cpu_colour_styles[(offset_position - show_avg_offset)
% self.colours.cpu_colour_styles.len()]
};
vec![GraphData {
points: &cpu.cpu_data[..],
style,
name: None,
}]
} else {
vec![]
}
};
// TODO: Maybe hide load avg if too long? Or maybe the CPU part. // TODO: Maybe hide load avg if too long? Or maybe the CPU part.
let title = if cfg!(target_family = "unix") { let title = if cfg!(target_family = "unix") {

View File

@ -24,7 +24,6 @@ impl Painter {
}; };
let margined_draw_loc = TextTable { let margined_draw_loc = TextTable {
table_gap: app_state.app_config_fields.table_gap, table_gap: app_state.app_config_fields.table_gap,
table_height_offset: self.table_height_offset,
is_force_redraw: app_state.is_force_redraw, is_force_redraw: app_state.is_force_redraw,
recalculate_column_widths, recalculate_column_widths,
header_style: self.colours.table_header_style, header_style: self.colours.table_header_style,

View File

@ -24,7 +24,6 @@ impl Painter {
}; };
let margined_draw_loc = TextTable { let margined_draw_loc = TextTable {
table_gap: app_state.app_config_fields.table_gap, table_gap: app_state.app_config_fields.table_gap,
table_height_offset: self.table_height_offset,
is_force_redraw: app_state.is_force_redraw, is_force_redraw: app_state.is_force_redraw,
recalculate_column_widths, recalculate_column_widths,
header_style: self.colours.table_header_style, header_style: self.colours.table_header_style,