summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2022-04-29 15:47:05 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2022-05-07 20:53:52 -0400
commit2a65bc95fe7a20e81725e6f559aee015fba852cc (patch)
treec6fc078c5a2b99477bedf724c6904f02a79a4fc9 /src
parent45680dafcff0c0da3610562b972b3f9cc14f6581 (diff)
refactor: consolidate disk and temp table drawing, refactor state
Disk and temp tables now share the same drawing logic, as well as consolidating the "text table" states into one single state, as opposed to two separate states (one for scroll and one for width calculations). BTW I know this is kinda an ugly design - creating a giant struct to call a function - hopefully that's temporary, I want to do a bigger refactor to consolidate more stuff together and therefore avoid this problem, but baby steps, right?
Diffstat (limited to 'src')
-rw-r--r--src/app.rs39
-rw-r--r--src/app/states.rs238
-rw-r--r--src/canvas.rs6
-rw-r--r--src/canvas/components/text_table.rs256
-rw-r--r--src/canvas/components/time_chart.rs20
-rw-r--r--src/canvas/components/time_graph.rs1
-rw-r--r--src/canvas/drawing_utils.rs8
-rw-r--r--src/canvas/widgets/disk_table.rs260
-rw-r--r--src/canvas/widgets/temp_table.rs251
-rw-r--r--src/data_conversion.rs40
-rw-r--r--src/options.rs4
11 files changed, 589 insertions, 534 deletions
diff --git a/src/app.rs b/src/app.rs
index 5f9ac413..8e49dd3a 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2216,8 +2216,8 @@ impl App {
.temp_state
.get_mut_widget_state(self.current_widget.widget_id)
{
- temp_widget_state.scroll_state.current_scroll_position = 0;
- temp_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
+ temp_widget_state.table_state.current_scroll_position = 0;
+ temp_widget_state.table_state.scroll_direction = ScrollDirection::Up;
}
}
BottomWidgetType::Disk => {
@@ -2225,8 +2225,8 @@ impl App {
.disk_state
.get_mut_widget_state(self.current_widget.widget_id)
{
- disk_widget_state.scroll_state.current_scroll_position = 0;
- disk_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
+ disk_widget_state.table_state.current_scroll_position = 0;
+ disk_widget_state.table_state.scroll_direction = ScrollDirection::Up;
}
}
BottomWidgetType::CpuLegend => {
@@ -2286,10 +2286,10 @@ impl App {
.temp_state
.get_mut_widget_state(self.current_widget.widget_id)
{
- if !self.canvas_data.temp_sensor_data.is_empty() {
- temp_widget_state.scroll_state.current_scroll_position =
- self.canvas_data.temp_sensor_data.len() - 1;
- temp_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
+ if !self.canvas_data.temp_sensor_data.data.is_empty() {
+ temp_widget_state.table_state.current_scroll_position =
+ self.canvas_data.temp_sensor_data.data.len() - 1;
+ temp_widget_state.table_state.scroll_direction = ScrollDirection::Down;
}
}
}
@@ -2298,10 +2298,10 @@ impl App {
.disk_state
.get_mut_widget_state(self.current_widget.widget_id)
{
- if !self.canvas_data.disk_data.is_empty() {
- disk_widget_state.scroll_state.current_scroll_position =
- self.canvas_data.disk_data.len() - 1;
- disk_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
+ if !self.canvas_data.disk_data.data.is_empty() {
+ disk_widget_state.table_state.current_scroll_position =
+ self.canvas_data.disk_data.data.len() - 1;
+ disk_widget_state.table_state.scroll_direction = ScrollDirection::Down;
}
}
}
@@ -2419,9 +2419,10 @@ impl App {
.widget_states
.get_mut(&self.current_widget.widget_id)
{
- temp_widget_state
- .scroll_state
- .update_position(num_to_change_by, self.canvas_data.temp_sensor_data.len());
+ temp_widget_state.table_state.update_position(
+ num_to_change_by,
+ self.canvas_data.temp_sensor_data.data.len(),
+ );
}
}
@@ -2432,8 +2433,8 @@ impl App {
.get_mut(&self.current_widget.widget_id)
{
disk_widget_state
- .scroll_state
- .update_position(num_to_change_by, self.canvas_data.disk_data.len());
+ .table_state
+ .update_position(num_to_change_by, self.canvas_data.disk_data.data.len());
}
}
@@ -2981,7 +2982,7 @@ impl App {
.get_widget_state(self.current_widget.widget_id)
{
if let Some(visual_index) =
- temp_widget_state.scroll_state.table_state.selected()
+ temp_widget_state.table_state.table_state.selected()
{
self.change_temp_position(
offset_clicked_entry as i64 - visual_index as i64,
@@ -2995,7 +2996,7 @@ impl App {
.get_widget_state(self.current_widget.widget_id)
{
if let Some(visual_index) =
- disk_widget_state.scroll_state.table_state.selected()
+ disk_widget_state.table_state.table_state.selected()
{
self.change_disk_position(
offset_clicked_entry as i64 - visual_index as i64,
diff --git a/src/app/states.rs b/src/app/states.rs
index 8cbb8ae1..5d3ace9c 100644
--- a/src/app/states.rs
+++ b/src/app/states.rs
@@ -1,4 +1,4 @@
-use std::{collections::HashMap, convert::TryInto, time::Instant};
+use std::{borrow::Cow, collections::HashMap, convert::TryInto, time::Instant};
use unicode_segmentation::GraphemeCursor;
@@ -31,16 +31,173 @@ pub enum CursorDirection {
Right,
}
-/// AppScrollWidgetState deals with fields for a scrollable app's current state.
+/// Meant for canvas operations involving table column widths.
+#[derive(Default)]
+pub struct CanvasTableWidthState {
+ pub desired_column_widths: Vec<u16>,
+ pub calculated_column_widths: Vec<u16>,
+}
+
+/// A bound on the width of a column.
+#[derive(Clone, Copy, Debug)]
+pub enum WidthBounds {
+ /// A width of this type is either as long as `min`, but can otherwise shrink and grow up to a point.
+ Soft {
+ /// The minimum amount before giving up and hiding.
+ min_width: u16,
+
+ /// The desired, calculated width. Take this if possible as the base starting width.
+ desired: u16,
+
+ /// The max width, as a percentage of the total width available. If [`None`],
+ /// then it can grow as desired.
+ max_percentage: Option<f32>,
+ },
+
+ /// A width of this type is either as long as specified, or does not appear at all.
+ Hard(u16),
+}
+
+impl WidthBounds {
+ pub const fn soft_from_str(name: &'static str, max_percentage: Option<f32>) -> WidthBounds {
+ WidthBounds::Soft {
+ min_width: name.len() as u16,
+ desired: name.len() as u16,
+ max_percentage,
+ }
+ }
+}
+
+pub struct TableComponentColumn {
+ /// The name of the column. Displayed if possible as the header.
+ pub name: Cow<'static, str>,
+
+ /// 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.
+ pub width_bounds: WidthBounds,
+}
+
+impl TableComponentColumn {
+ pub fn new<I>(name: I, alt: Option<I>, width_bounds: WidthBounds) -> Self
+ where
+ I: Into<Cow<'static, str>>,
+ {
+ Self {
+ name: name.into(),
+ alt: alt.map(Into::into),
+ width_bounds,
+ }
+ }
+}
+
+/// [`TableComponentState`] deals with fields for a scrollable's current state.
#[derive(Default)]
-pub struct AppScrollWidgetState {
+pub struct TableComponentState {
pub current_scroll_position: usize,
pub scroll_bar: usize,
pub scroll_direction: ScrollDirection,
pub table_state: TableState,
+ pub columns: Vec<TableComponentColumn>,
+ pub calculated_widths: Vec<u16>,
}
-impl AppScrollWidgetState {
+impl TableComponentState {
+ pub fn new(columns: Vec<TableComponentColumn>) -> Self {
+ Self {
+ current_scroll_position: 0,
+ scroll_bar: 0,
+ scroll_direction: ScrollDirection::Down,
+ table_state: Default::default(),
+ columns,
+ calculated_widths: Vec::default(),
+ }
+ }
+
+ /// Calculates widths for the columns for this table.
+ ///
+ /// * `total_width` is the, well, total width available. **NOTE:** This function automatically
+ /// 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
+ /// false.
+ ///
+ /// **NOTE:** Trailing 0's may break tui-rs, remember to filter them out later!
+ pub fn calculate_column_widths(&mut self, total_width: u16, left_to_right: bool) {
+ use itertools::Either;
+ use std::cmp::{max, min};
+
+ if total_width > 2 {
+ let initial_width = total_width - 2;
+ let mut total_width_left = initial_width;
+
+ let column_widths = &mut self.calculated_widths;
+ *column_widths = vec![0; self.columns.len()];
+
+ let columns = if left_to_right {
+ Either::Left(self.columns.iter().enumerate())
+ } else {
+ Either::Right(self.columns.iter().enumerate().rev())
+ };
+
+ for (itx, column) in columns {
+ match &column.width_bounds {
+ WidthBounds::Soft {
+ min_width,
+ desired,
+ max_percentage,
+ } => {
+ let soft_limit = max(
+ if let Some(max_percentage) = max_percentage {
+ // Rust doesn't have an `into()` or `try_into()` for floats to integers???
+ ((*max_percentage * f32::from(initial_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 {
+ 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);
+
+ if *width > space_taken {
+ break;
+ } else {
+ total_width_left = total_width_left.saturating_sub(space_taken + 1);
+ column_widths[itx] = space_taken;
+ }
+ }
+ }
+ }
+
+ while let Some(0) = column_widths.last() {
+ column_widths.pop();
+ }
+
+ if !column_widths.is_empty() {
+ // Redistribute remaining.
+ let amount_per_slot = 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() {
+ if index < total_width_left.into() {
+ *width += amount_per_slot + 1;
+ } else {
+ *width += amount_per_slot;
+ }
+ }
+ }
+ }
+ }
+
/// Updates the position if possible, and if there is a valid change, returns the new position.
pub fn update_position(&mut self, change: i64, num_entries: usize) -> Option<usize> {
if change == 0 {
@@ -159,13 +316,6 @@ impl AppSearchState {
}
}
-/// Meant for canvas operations involving table column widths.
-#[derive(Default)]
-pub struct CanvasTableWidthState {
- pub desired_column_widths: Vec<u16>,
- pub calculated_column_widths: Vec<u16>,
-}
-
/// ProcessSearchState only deals with process' search's current settings and state.
pub struct ProcessSearchState {
pub search_state: AppSearchState,
@@ -426,7 +576,8 @@ impl ProcColumn {
/// NOTE: ALWAYS call this when opening the sorted window.
pub fn set_to_sorted_index_from_type(&mut self, proc_sorting_type: &ProcessSorting) {
- // TODO [Custom Columns]: If we add custom columns, this may be needed! Since column indices will change, this runs the risk of OOB. So, when you change columns, CALL THIS AND ADAPT!
+ // TODO [Custom Columns]: If we add custom columns, this may be needed!
+ // Since column indices will change, this runs the risk of OOB. So, if you change columns, CALL THIS AND ADAPT!
let mut true_index = 0;
for column in &self.ordered_columns {
if *column == *proc_sorting_type {
@@ -489,7 +640,7 @@ impl ProcColumn {
pub struct ProcWidgetState {
pub process_search_state: ProcessSearchState,
pub is_grouped: bool,
- pub scroll_state: AppScrollWidgetState,
+ pub scroll_state: TableComponentState,
pub process_sorting_type: processes::ProcessSorting,
pub is_process_sort_descending: bool,
pub is_using_command: bool,
@@ -546,7 +697,7 @@ impl ProcWidgetState {
ProcWidgetState {
process_search_state,
is_grouped,
- scroll_state: AppScrollWidgetState::default(),
+ scroll_state: TableComponentState::default(),
process_sorting_type,
is_process_sort_descending,
is_using_command,
@@ -774,7 +925,7 @@ pub struct CpuWidgetState {
pub current_display_time: u64,
pub is_legend_hidden: bool,
pub autohide_timer: Option<Instant>,
- pub scroll_state: AppScrollWidgetState,
+ pub scroll_state: TableComponentState,
pub is_multi_graph_mode: bool,
pub table_width_state: CanvasTableWidthState,
}
@@ -785,7 +936,7 @@ impl CpuWidgetState {
current_display_time,
is_legend_hidden: false,
autohide_timer,
- scroll_state: AppScrollWidgetState::default(),
+ scroll_state: TableComponentState::default(),
is_multi_graph_mode: false,
table_width_state: CanvasTableWidthState::default(),
}
@@ -850,15 +1001,25 @@ impl MemState {
}
pub struct TempWidgetState {
- pub scroll_state: AppScrollWidgetState,
- pub table_width_state: CanvasTableWidthState,
+ pub table_state: TableComponentState,
}
-impl TempWidgetState {
- pub fn init() -> Self {
+impl Default for TempWidgetState {
+ fn default() -> Self {
+ const TEMP_HEADERS: [&str; 2] = ["Sensor", "Temp"];
+ const WIDTHS: [WidthBounds; TEMP_HEADERS.len()] = [
+ WidthBounds::soft_from_str(TEMP_HEADERS[0], Some(0.8)),
+ WidthBounds::soft_from_str(TEMP_HEADERS[1], None),
+ ];
+
TempWidgetState {
- scroll_state: AppScrollWidgetState::default(),
- table_width_state: CanvasTableWidthState::default(),
+ table_state: TableComponentState::new(
+ TEMP_HEADERS
+ .iter()
+ .zip(WIDTHS)
+ .map(|(header, width)| TableComponentColumn::new(*header, None, width))
+ .collect(),
+ ),
}
}
}
@@ -882,15 +1043,30 @@ impl TempState {
}
pub struct DiskWidgetState {
- pub scroll_state: AppScrollWidgetState,
- pub table_width_state: CanvasTableWidthState,
+ pub table_state: TableComponentState,
}
-impl DiskWidgetState {
- pub fn init() -> Self {
+impl Default for DiskWidgetState {
+ fn default() -> Self {
+ const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"];
+ const WIDTHS: [WidthBounds; DISK_HEADERS.len()] = [
+ WidthBounds::soft_from_str(DISK_HEADERS[0], Some(0.2)),
+ WidthBounds::soft_from_str(DISK_HEADERS[1], Some(0.2)),
+ WidthBounds::Hard(4),
+ WidthBounds::Hard(6),
+ WidthBounds::Hard(6),
+ WidthBounds::Hard(7),
+ WidthBounds::Hard(7),
+ ];
+
DiskWidgetState {
- scroll_state: AppScrollWidgetState::default(),
- table_width_state: CanvasTableWidthState::default(),
+ table_state: TableComponentState::new(
+ DISK_HEADERS
+ .iter()
+ .zip(WIDTHS)
+ .map(|(header, width)| TableComponentColumn::new(*header, None, width))
+ .collect(),
+ ),
}
}
}
@@ -978,18 +1154,20 @@ mod test {
#[test]
fn test_scroll_update_position() {
fn check_scroll_update(
- scroll: &mut AppScrollWidgetState, change: i64, max: usize, ret: Option<usize>,
+ scroll: &mut TableComponentState, change: i64, max: usize, ret: Option<usize>,
new_position: usize,
) {
assert_eq!(scroll.update_position(change, max), ret);
assert_eq!(scroll.current_scroll_position, new_position);
}
- let mut scroll = AppScrollWidgetState {
+ let mut scroll = TableComponentState {
current_scroll_position: 5,
scroll_bar: 0,
scroll_direction: ScrollDirection::Down,
table_state: Default::default(),
+ columns: vec![],
+ calculated_widths: vec![],
};
let s = &mut scroll;
diff --git a/src/canvas.rs b/src/canvas.rs
index 2070327a..21cd5197 100644
--- a/src/canvas.rs
+++ b/src/canvas.rs
@@ -18,7 +18,7 @@ use crate::{
App,
},
constants::*,
- data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData},
+ data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData, TableData},
options::Config,
utils::error,
utils::error::BottomError,
@@ -41,8 +41,8 @@ pub struct DisplayableData {
pub total_tx_display: String,
pub network_data_rx: Vec<Point>,
pub network_data_tx: Vec<Point>,
- pub disk_data: Vec<Vec<String>>,
- pub temp_sensor_data: Vec<Vec<String>>,
+ pub disk_data: TableData,
+ pub temp_sensor_data: TableData,
pub single_process_data: HashMap<Pid, ConvertedProcessData>, // Contains single process data, key is PID
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed, key is the widget ID.
pub stringified_process_data_map: HashMap<u64, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled, key is the widget ID
diff --git a/src/canvas/components/text_table.rs b/src/canvas/components/text_table.rs
index 8b137891..16e049ed 100644
--- a/src/canvas/components/text_table.rs
+++ b/src/canvas/components/text_table.rs
@@ -1 +1,257 @@
+use std::borrow::Cow;
+use concat_string::concat_string;
+use tui::{
+ backend::Backend,
+ layout::{Constraint, Direction, Layout, Rect},
+ style::Style,
+ text::{Span, Spans, Text},
+ widgets::{Block, Borders, Row, Table},
+ Frame,
+};
+use unicode_segmentation::UnicodeSegmentation;
+
+use crate::{
+ app::{self, TableComponentState},
+ constants::{SIDE_BORDERS, TABLE_GAP_HEIGHT_LIMIT},
+ data_conversion::TableData,
+};
+
+pub struct TextTable<'a> {
+ pub table_gap: u16,
+ pub table_height_offset: u16,
+ pub is_force_redraw: bool,
+ pub recalculate_column_widths: bool,
+
+ /// The header style.
+ pub header_style: Style,
+
+ /// The border style.
+ pub border_style: Style,
+
+ /// The highlighted entry style.
+ pub highlighted_style: Style,
+
+ /// The graph title.
+ pub title: Cow<'a, str>,
+
+ /// Whether this graph is expanded.
+ pub is_expanded: bool,
+
+ /// Whether this widget is selected.
+ pub is_on_widget: bool,
+
+ /// Whether to draw all borders.
+ pub draw_border: bool,
+
+ /// Whether to show the scroll position.
+ pub show_table_scroll_position: bool,
+
+ /// The title style.
+ pub title_style: Style,
+
+ /// The text style.
+ pub text_style: Style,
+
+ /// Whether to determine widths from left to right.
+ pub left_to_right: bool,
+}
+
+impl<'a> TextTable<'a> {
+ /// Generates a title for the [`TextTable`] widget, given the available space.
+ fn generate_title(&self, draw_loc: Rect, pos: usize, total: usize) -> Spans<'_> {
+ let title = if self.show_table_scroll_position {
+ let title_string = concat_string!(
+ self.title,
+ "(",
+ pos.to_string(),
+ " of ",
+ total.to_string(),
+ ") "
+ );
+
+ if title_string.len() + 2 <= draw_loc.width.into() {
+ title_string
+ } else {
+ self.title.to_string()
+ }
+ } else {
+ self.title.to_string()
+ };
+
+ if self.is_expanded {
+ let title_base = concat_string!(title, "── Esc to go back ");
+ let esc = concat_string!(
+ "─",
+ "─".repeat(usize::from(draw_loc.width).saturating_sub(
+ UnicodeSegmentation::graphemes(title_base.as_str(), true).count() + 2
+ )),
+ "─ Esc to go back "
+ );
+ Spans::from(vec![
+ Span::styled(title, self.title_style),
+ Span::styled(esc, self.border_style),
+ ])
+ } else {
+ Spans::from(Span::styled(title, self.title_style))
+ }
+ }
+ pub fn draw_text_table<B: Backend>(
+ &self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState,
+ table_data: &TableData,
+ ) -> Rect {
+ let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
+ 0
+ } else {
+ self.table_gap
+ };
+ let start_position = get_start_position(
+ usize::from(
+ (draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset),
+ ),
+ &state.scroll_direction,
+ &mut state.scroll_bar,
+ state.current_scroll_position,
+ self.is_force_redraw,
+ );
+ state.table_state.select(Some(
+ state.current_scroll_position.saturating_sub(start_position),
+ ));
+ let sliced_vec = &table_data.data[start_position..];
+
+ // 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 {
+ Block::default()
+ .title(title)
+ .borders(Borders::ALL)
+ .border_style(self.border_style)
+ } else if self.is_on_widget {
+ Block::default()
+ .borders(SIDE_BORDERS)
+ .border_style(self.border_style)
+ } else {
+ Block::default().borders(Borders::NONE)
+ };
+
+ let margined_draw_loc = Layout::default()
+ .constraints([Constraint::Percentage(100)])
+ .horizontal_margin(if self.is_on_widget || self.draw_border {
+ 0
+ } else {
+ 1
+ })
+ .direction(Direction::Horizontal)
+ .split(draw_loc)[0];
+
+ // Draw!
+ f.render_stateful_widget(
+ Table::new(disk_rows)
+ .block(disk_block)
+ .header(header)
+ .highlight_style(self.highlighted_style)
+ .style(self.text_style)
+ .widths(
+ &(widths
+ .iter()
+ .map(|w| Constraint::Length(*w))
+ .collect::<Vec<_>>()),
+ ),
+ margined_draw_loc,
+ &mut state.table_state,
+ );
+
+ margined_draw_loc
+ }
+}
+
+/// Truncates text if it is too long, and adds an ellipsis at the end if needed.
+fn truncate_text(text: &str, width: usize) -> Text<'_> {
+ let graphemes = UnicodeSegmentation::graphemes(text, true).collect::<Vec<&str>>();
+ if graphemes.len() > width && width > 0 {
+ // Truncate with ellipsis
+ let first_n = graphemes[..(width - 1)].concat();
+ return Text::raw(concat_string!(first_n, "…"));
+ } else {
+ Text::raw(text)
+ }
+}
+
+/// Gets the starting position of a table.
+pub fn get_start_position(
+ num_rows: usize, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut usize,
+ currently_selected_position: usize, is_force_redraw: bool,
+) -> usize {
+ if is_force_redraw {
+ *scroll_position_bar = 0;
+ }
+
+ match scroll_direction {
+ app::ScrollDirection::Down => {
+ if currently_selected_position < *scroll_position_bar + num_rows {
+ // If, using previous_scrolled_position, we can see the element
+ // (so within that and + num_rows) just reuse the current previously scrolled position
+ *scroll_position_bar
+ } else if currently_selected_position >= num_rows {
+ // Else if the current position past the last element visible in the list, omit
+ // until we can see that element
+ *scroll_position_bar = currently_selected_position - num_rows;
+ *scroll_position_bar
+ } else {
+ // Else, if it is not past the last element visible, do not omit anything
+ 0
+ }
+ }
+ app::ScrollDirection::Up => {
+ if currently_selected_position <= *scroll_position_bar {
+ // If it's past the first element, then show from that element downwards
+ *scroll_position_bar = currently_selected_position;
+ } else if currently_selected_position >= *scroll_position_bar + num_rows {
+ *scroll_position_bar = currently_selected_position - num_rows;
+ }
+ // Else, don't change what our start position is from whatever it is set to!
+ *scroll_position_bar
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {}
diff --git a/src/canvas/components/time_chart.rs b/src/canvas/components/time_chart.rs
index 5bb0ff15..23b05219 100644
--- a/src/canvas/components/time_chart.rs
+++ b/src/canvas/components/time_chart.rs
@@ -622,19 +622,19 @@ mod test {
}
#[test]
- fn time_chart_test_data_trimming() {
- // Quick test on a completely empty dataset...
- {
- let data = [];
- let dataset = Dataset::default().data(&data);
+ fn time_chart_empty_dataset() {
+ let data = [];
+ let dataset = Dataset::default().data(&data);
- assert_eq!(get_start(&dataset, -100.0), (0, None));
- assert_eq!(get_start(&dataset, -3.0), (0, None));
+ assert_eq!(get_start(&dataset, -100.0), (0, None));
+ assert_eq!(get_start(&dataset, -3.0), (0, None));
- assert_eq!(get_end(&dataset, 0.0), (0, None));
- assert_eq!(get_end(&dataset, 100.0), (0, None));
- }
+ assert_eq!(get_end(&dataset, 0.0), (0, None));
+ assert_eq!(get_end(&dataset, 100.0), (0, None));
+ }
+ #[test]
+ fn time_chart_test_data_trimming() {
let data = [
(-3.0, 8.0),
(-2.5, 15.0),
diff --git a/src/canvas/components/time_graph.rs b/src/canvas/components/time_graph.rs
index ab5cec2e..723483be 100644
--- a/src/canvas/components/time_graph.rs
+++ b/src/canvas/components/time_graph.rs
@@ -25,7 +25,6 @@ pub struct GraphData<'a> {
pub name: Option<Cow<'a, str>>,
}
-#[derive(Default)]
pub struct TimeGraph<'a> {
/// Whether to use a dot marker over the default braille markers.
pub use_dot: bool,
diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs
index f7b81281..839017a9 100644
--- a/src/canvas/drawing_utils.rs
+++ b/src/canvas/drawing_utils.rs
@@ -1,6 +1,6 @@
use tui::layout::Rect;
-use crate::app;
+use crate::app::{self};
use std::{
cmp::{max, min},
time::Instant,
@@ -329,6 +329,12 @@ mod test {
}
#[test]
+ fn test_width_calculation() {
+ // TODO: Implement width calculation test; can reuse old ones as basis
+ todo!()
+ }
+
+ #[test]
fn test_zero_width() {
assert_eq!(
get_column_widths(
diff --git a/src/canvas/widgets/disk_table.rs b/src/canvas/widgets/disk_table.rs
index 252ec70b..8c7adc7d 100644
--- a/src/canvas/widgets/disk_table.rs
+++ b/src/canvas/widgets/disk_table.rs
@@ -1,31 +1,9 @@
-use once_cell::sync::Lazy;
-use tui::{
- backend::Backend,
- layout::{Constraint, Direction, Layout, Rect},
- terminal::Frame,
- text::Span,
- text::{Spans, Text},
- widgets::{Block, Borders, Row, Table},
-};
+use tui::{backend::Backend, layout::Rect, terminal::Frame};
use crate::{
app,
- canvas::{
- drawing_utils::{get_column_widths, get_start_position},
- Painter,
- },
- constants::*,
+ canvas::{components::TextTable, Painter},
};
-use unicode_segmentation::UnicodeSegmentation;
-
-const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"];
-
-static DISK_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
- DISK_HEADERS
- .iter()
- .map(|entry| entry.len() as u16)
- .collect::<Vec<_>>()
-});
impl Painter {
pub fn draw_disk_table<B: Backend>(
@@ -34,120 +12,9 @@ impl Painter {
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
- let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
- 0
- } else {
- app_state.app_config_fields.table_gap
- };
- let start_position = get_start_position(
- usize::from(
- (draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset),
- ),
- &disk_widget_state.scroll_state.scroll_direction,
- &mut disk_widget_state.scroll_state.scroll_bar,
- disk_widget_state.scroll_state.current_scroll_position,
- app_state.is_force_redraw,