diff options
author | ClementTsang <cjhtsang@uwaterloo.ca> | 2022-05-07 03:38:55 -0400 |
---|---|---|
committer | ClementTsang <cjhtsang@uwaterloo.ca> | 2022-05-15 21:02:28 -0400 |
commit | 7ee6f6a7373c915bde9e0c3860456b0aba6fdc8c (patch) | |
tree | e369edb0960029a9889b8c37d7aa3541705068bd /src/canvas | |
parent | 69ec526dc642ad5754d6ef8d073f85fc805ea00f (diff) |
refactor: begin migration of process widget
Diffstat (limited to 'src/canvas')
-rw-r--r-- | src/canvas/components/text_table.rs | 206 | ||||
-rw-r--r-- | src/canvas/dialogs/dd_dialog.rs | 10 | ||||
-rw-r--r-- | src/canvas/drawing_utils.rs | 256 | ||||
-rw-r--r-- | src/canvas/widgets/cpu_graph.rs | 4 | ||||
-rw-r--r-- | src/canvas/widgets/process_table.rs | 703 |
5 files changed, 385 insertions, 794 deletions
diff --git a/src/canvas/components/text_table.rs b/src/canvas/components/text_table.rs index cf9788ad..f75a5eb3 100644 --- a/src/canvas/components/text_table.rs +++ b/src/canvas/components/text_table.rs @@ -1,4 +1,7 @@ -use std::{borrow::Cow, cmp::min}; +use std::{ + borrow::Cow, + cmp::{max, min}, +}; use concat_string::concat_string; use tui::{ @@ -12,9 +15,12 @@ use tui::{ use unicode_segmentation::UnicodeSegmentation; use crate::{ - app::{self, TableComponentState}, + app::{ + self, CellContent, SortState, TableComponentColumn, TableComponentHeader, + TableComponentState, + }, constants::{SIDE_BORDERS, TABLE_GAP_HEIGHT_LIMIT}, - data_conversion::{CellContent, TableData, TableRow}, + data_conversion::{TableData, TableRow}, }; pub struct TextTableTitle<'a> { @@ -101,8 +107,8 @@ impl<'a> TextTable<'a> { } }) } - pub fn draw_text_table<B: Backend>( - &self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState, + pub fn draw_text_table<B: Backend, H: TableComponentHeader>( + &self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState<H>, table_data: &TableData, ) { // TODO: This is a *really* ugly hack to get basic mode to hide the border when not selected, without shifting everything. @@ -179,7 +185,7 @@ impl<'a> TextTable<'a> { desired, max_percentage: _, } => { - *desired = std::cmp::max(column.name.len(), *data_width) as u16; + *desired = max(column.header.header_text().len(), *data_width) as u16; } app::WidthBounds::Hard(_width) => {} }); @@ -188,15 +194,9 @@ impl<'a> TextTable<'a> { } let columns = &state.columns; - let header = Row::new(columns.iter().filter_map(|c| { - if c.calculated_width == 0 { - None - } else { - Some(truncate_text(&c.name, c.calculated_width.into(), None)) - } - })) - .style(self.header_style) - .bottom_margin(table_gap); + let header = build_header(columns, &state.sort_state) + .style(self.header_style) + .bottom_margin(table_gap); let table_rows = sliced_vec.iter().map(|row| { let (row, style) = match row { TableRow::Raw(row) => (row, None), @@ -245,9 +245,60 @@ impl<'a> TextTable<'a> { } } +/// Constructs the table header. +fn build_header<'a, H: TableComponentHeader>( + columns: &'a [TableComponentColumn<H>], sort_state: &SortState, +) -> Row<'a> { + use itertools::Either; + + const UP_ARROW: &str = "▲"; + const DOWN_ARROW: &str = "▼"; + + let iter = match sort_state { + SortState::Unsortable => Either::Left(columns.iter().filter_map(|c| { + if c.calculated_width == 0 { + None + } else { + Some(truncate_text( + c.header.header_text(), + c.calculated_width.into(), + None, + )) + } + })), + SortState::Sortable { index, order } => { + let arrow = match order { + app::SortOrder::Ascending => UP_ARROW, + app::SortOrder::Descending => DOWN_ARROW, + }; + + Either::Right(columns.iter().enumerate().filter_map(move |(itx, c)| { + if c.calculated_width == 0 { + None + } else if itx == *index { + Some(truncate_suffixed_text( + c.header.header_text(), + arrow, + c.calculated_width.into(), + None, + )) + } else { + Some(truncate_text( + c.header.header_text(), + c.calculated_width.into(), + None, + )) + } + })) + } + }; + + Row::new(iter) +} + /// Truncates text if it is too long, and adds an ellipsis at the end if needed. -fn truncate_text(content: &CellContent, width: usize, row_style: Option<Style>) -> Text<'_> { - let (text, opt) = match content { +fn truncate_text<'a>(content: &'a CellContent, width: usize, row_style: Option<Style>) -> Text<'a> { + let (main_text, alt_text) = match content { CellContent::Simple(s) => (s, None), CellContent::HasAlt { alt: short, @@ -255,18 +306,57 @@ fn truncate_text(content: &CellContent, width: usize, row_style: Option<Style>) } => (long, Some(short)), }; - let graphemes = UnicodeSegmentation::graphemes(text.as_ref(), true).collect::<Vec<&str>>(); - let mut text = if graphemes.len() > width && width > 0 { - if let Some(s) = opt { - // If an alternative exists, use that. - Text::raw(s.as_ref()) + let mut text = { + let graphemes: Vec<&str> = + UnicodeSegmentation::graphemes(main_text.as_ref(), true).collect(); + if graphemes.len() > width && width > 0 { + if let Some(s) = alt_text { + // If an alternative exists, use that. + Text::raw(s.as_ref()) + } else { + // Truncate with ellipsis + let first_n = graphemes[..(width - 1)].concat(); + Text::raw(concat_string!(first_n, "…")) + } } else { - // Truncate with ellipsis - let first_n = graphemes[..(width - 1)].concat(); - Text::raw(concat_string!(first_n, "…")) + Text::raw(main_text.as_ref()) + } + }; + + if let Some(row_style) = row_style { + text.patch_style(row_style); + } + + text +} + +fn truncate_suffixed_text<'a>( + content: &'a CellContent, suffix: &str, width: usize, row_style: Option<Style>, +) -> Text<'a> { + let (main_text, alt_text) = match content { + CellContent::Simple(s) => (s, None), + CellContent::HasAlt { + alt: short, + main: long, + } => (long, Some(short)), + }; + + let mut text = { + let suffixed = concat_string!(main_text, suffix); + let graphemes: Vec<&str> = + UnicodeSegmentation::graphemes(suffixed.as_str(), true).collect(); + if graphemes.len() > width && width > 1 { + if let Some(alt) = alt_text { + // If an alternative exists, use that + arrow. + Text::raw(concat_string!(alt, suffix)) + } else { + // Truncate with ellipsis + arrow. + let first_n = graphemes[..(width - 2)].concat(); + Text::raw(concat_string!(first_n, "…", suffix)) + } + } else { + Text::raw(suffixed) } - } else { - Text::raw(text.as_ref()) }; if let Some(row_style) = row_style { @@ -315,4 +405,64 @@ pub fn get_start_position( } #[cfg(test)] -mod test {} +mod test { + use super::*; + + #[test] + fn test_get_start_position() { + use crate::app::ScrollDirection::{self, Down, Up}; + + #[track_caller] + + fn test_get( + bar: usize, num: usize, direction: ScrollDirection, selected: usize, force: bool, + expected_posn: usize, expected_bar: usize, + ) { + let mut bar = bar; + assert_eq!( + get_start_position(num, &direction, &mut bar, selected, force), + expected_posn + ); + assert_eq!(bar, expected_bar); + } + + // Scrolling down from start + test_get(0, 10, Down, 0, false, 0, 0); + + // Simple scrolling down + test_get(0, 10, Down, 1, false, 0, 0); + + // Scrolling down from the middle high up + test_get(0, 10, Down, 5, false, 0, 0); + + // Scrolling down into boundary + test_get(0, 10, Down, 11, false, 1, 1); + + // Scrolling down from the with non-zero bar + test_get(5, 10, Down, 15, false, 5, 5); + + // Force redraw scrolling down (e.g. resize) + test_get(5, 15, Down, 15, true, 0, 0); + + // Test jumping down + test_get(1, 10, Down, 20, true, 10, 10); + + // Scrolling up from bottom + test_get(10, 10, Up, 20, false, 10, 10); + + // Simple scrolling up + test_get(10, 10, Up, 19, false, 10, 10); + + // Scrolling up from the middle + test_get(10, 10, Up, 10, false, 10, 10); + + // Scrolling up into boundary + test_get(10, 10, Up, 9, false, 9, 9); + + // Force redraw scrolling up (e.g. resize) + test_get(5, 10, Up, 15, true, 5, 5); + + // Test jumping up + test_get(10, 10, Up, 0, false, 0, 0); + } +} diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index f7edbedd..3435e381 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -9,7 +9,7 @@ use tui::{ }; use crate::{ - app::{App, KillSignal}, + app::{App, KillSignal, widgets::ProcWidgetMode}, canvas::Painter, }; @@ -29,7 +29,13 @@ impl Painter { if let Some(first_pid) = to_kill_processes.1.first() { return Some(Text::from(vec![ Spans::from(""), - if app_state.is_grouped(app_state.current_widget.widget_id) { + if app_state + .proc_state + .widget_states + .get(&app_state.current_widget.widget_id) + .map(|p| matches!(p.mode, ProcWidgetMode::Grouped)) + .unwrap_or(false) + { if to_kill_processes.1.len() != 1 { Spans::from(format!( "Kill {} processes with the name \"{}\"? Press ENTER to confirm.", diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs index 852a18da..126ff0ec 100644 --- a/src/canvas/drawing_utils.rs +++ b/src/canvas/drawing_utils.rs @@ -6,117 +6,6 @@ use std::{ time::Instant, }; -/// Return a (hard)-width vector for column widths. -/// -/// * `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. -/// * `hard_widths` is inflexible column widths. Use a `None` to represent a soft width. -/// * `soft_widths_min` is the lower limit for a soft width. Use `None` if a hard width goes there. -/// * `soft_widths_max` is the upper limit for a soft width, in percentage of the total width. Use -/// `None` if a hard width goes there. -/// * `soft_widths_desired` is the desired soft width. Use `None` if a hard width goes there. -/// * `left_to_right` is a boolean whether to go from left to right if true, or right to left if -/// false. -/// -/// **NOTE:** This function ASSUMES THAT ALL PASSED SLICES ARE OF THE SAME SIZE. -/// -/// **NOTE:** The returned vector may not be the same size as the slices, this is because including -/// 0-constraints breaks tui-rs. -pub fn get_column_widths( - total_width: u16, hard_widths: &[Option<u16>], soft_widths_min: &[Option<u16>], - soft_widths_max: &[Option<f64>], soft_widths_desired: &[Option<u16>], left_to_right: bool, -) -> Vec<u16> { - debug_assert!( - hard_widths.len() == soft_widths_min.len(), - "hard width length != soft width min length!" - ); - debug_assert!( - soft_widths_min.len() == soft_widths_max.len(), - "soft width min length != soft width max length!" - ); - debug_assert!( - soft_widths_max.len() == soft_widths_desired.len(), - "soft width max length != soft width desired length!" - ); - - if total_width > 2 { - let initial_width = total_width - 2; - let mut total_width_left = initial_width; - let mut column_widths: Vec<u16> = vec![0; hard_widths.len()]; - let range: Vec<usize> = if left_to_right { - (0..hard_widths.len()).collect() - } else { - (0..hard_widths.len()).rev().collect() - }; - - for itx in &range { - if let Some(Some(hard_width)) = hard_widths.get(*itx) { - // Hard width... - let space_taken = min(*hard_width, total_width_left); - - // TODO [COLUMN MOVEMENT]: Remove this - if *hard_width > space_taken { - break; - } - - column_widths[*itx] = space_taken; - total_width_left -= space_taken; - total_width_left = total_width_left.saturating_sub(1); - } else if let ( - Some(Some(soft_width_max)), - Some(Some(soft_width_min)), - Some(Some(soft_width_desired)), - ) = ( - soft_widths_max.get(*itx), - soft_widths_min.get(*itx), - soft_widths_desired.get(*itx), - ) { - // Soft width... - let soft_limit = max( - if soft_width_max.is_sign_negative() { - *soft_width_desired - } else { - (*soft_width_max * initial_width as f64).ceil() as u16 - }, - *soft_width_min, - ); - let space_taken = min(min(soft_limit, *soft_width_desired), total_width_left); - - // TODO [COLUMN MOVEMENT]: Remove this - if *soft_width_min > space_taken { - break; - } - - column_widths[*itx] = space_taken; - total_width_left -= space_taken; - total_width_left = total_width_left.saturating_sub(1); - } - } - - 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; - } - } - } - - column_widths - } else { - vec![] - } -} - pub fn get_search_start_position( num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize, current_cursor_position: usize, is_force_redraw: bool, @@ -154,45 +43,6 @@ pub fn get_search_start_position( } } -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; - } - - // FIXME: Note that num_rows is WRONG here! It assumes the number of rows - 1... oops. - - 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 - } - } -} - /// Calculate how many bars are to be drawn within basic mode's components. pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize { std::cmp::min( @@ -227,62 +77,6 @@ mod test { use super::*; #[test] - fn test_get_start_position() { - use crate::app::ScrollDirection::{self, Down, Up}; - - fn test( - bar: usize, num: usize, direction: ScrollDirection, selected: usize, force: bool, - expected_posn: usize, expected_bar: usize, - ) { - let mut bar = bar; - assert_eq!( - get_start_position(num, &direction, &mut bar, selected, force), - expected_posn - ); - assert_eq!(bar, expected_bar); - } - - // Scrolling down from start - test(0, 10, Down, 0, false, 0, 0); - - // Simple scrolling down - test(0, 10, Down, 1, false, 0, 0); - - // Scrolling down from the middle high up - test(0, 10, Down, 5, false, 0, 0); - - // Scrolling down into boundary - test(0, 10, Down, 11, false, 1, 1); - - // Scrolling down from the with non-zero bar - test(5, 10, Down, 15, false, 5, 5); - - // Force redraw scrolling down (e.g. resize) - test(5, 15, Down, 15, true, 0, 0); - - // Test jumping down - test(1, 10, Down, 20, true, 10, 10); - - // Scrolling up from bottom - test(10, 10, Up, 20, false, 10, 10); - - // Simple scrolling up - test(10, 10, Up, 19, false, 10, 10); - - // Scrolling up from the middle - test(10, 10, Up, 10, false, 10, 10); - - // Scrolling up into boundary - test(10, 10, Up, 9, false, 9, 9); - - // Force redraw scrolling up (e.g. resize) - test(5, 10, Up, 15, true, 5, 5); - - // Test jumping up - test(10, 10, Up, 0, false, 0, 0); - } - - #[test] fn test_calculate_basic_use_bars() { // Testing various breakpoints and edge cases. assert_eq!(calculate_basic_use_bars(0.0, 15), 0); @@ -329,54 +123,4 @@ mod test { )); assert!(over_timer.is_none()); } - - #[test] - fn test_width_calculation() { - // TODO: Implement width calculation test; can reuse old ones as basis - } - - #[test] - fn test_zero_width() { - assert_eq!( - get_column_widths( - 0, - &[Some(1), None, None], - &[None, Some(1), Some(2)], - &[None, Some(0.125), Some(0.5)], - &[None, Some(10), Some(10)], - true - ), - vec![], - ); - } - - #[test] - fn test_two_width() { - assert_eq!( - get_column_widths( - 2, - &[Some(1), None, None], - &[None, Some(1), Some(2)], - &[None, Some(0.125), Some(0.5)], - &[None, Some(10), Some(10)], - true - ), - vec![], - ); - } - - #[test] - fn test_non_zero_width() { - assert_eq!( - get_column_widths( - 16, - &[Some(1), None, None], - &[None, Some(1), Some(2)], - &[None, Some(0.125), Some(0.5)], - &[None, Some(10), Some(10)], - true - ), - vec![2, 2, 7], - ); - } } diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 9efd0e10..e839c12a 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -1,13 +1,13 @@ use std::{borrow::Cow, iter}; use crate::{ - app::{layout_manager::WidgetDirection, App, CpuWidgetState}, + app::{layout_manager::WidgetDirection, App, CellContent, CpuWidgetState}, canvas::{ components::{GraphData, TextTable, TimeGraph}, drawing_utils::should_hide_x_label, Painter, }, - data_conversion::{CellContent, ConvertedCpuData, TableData, TableRow}, + data_conversion::{ConvertedCpuData, TableData, TableRow}, }; use concat_string::concat_string; diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 4e7ca9f9..c5e892a0 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -1,7 +1,8 @@ use crate::{ app::App, canvas::{ - drawing_utils::{get_column_widths, get_search_start_position, get_start_position}, + components::{TextTable, TextTableTitle}, + drawing_utils::get_search_start_position, Painter, }, constants::*, @@ -11,8 +12,8 @@ use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect}, terminal::Frame, - text::{Span, Spans, Text}, - widgets::{Block, Borders, Paragraph, Row, Table}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, }; use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation}; @@ -90,14 +91,14 @@ const PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: &[Option<f64>] = &[ impl Painter { /// Draws and handles all process-related drawing. Use this. /// - `widget_id` here represents the widget ID of the process widget itself! - pub fn draw_process_features<B: Backend>( + pub fn draw_process_widget<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, widget_id: u64, ) { if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) { let search_height = if draw_border { 5 } else { 3 }; let is_sort_open = process_widget_state.is_sort_open; - let header_len = process_widget_state.columns.longest_header_len; + const SORT_MENU_WIDTH: u16 = 8; let mut proc_draw_loc = draw_loc; if process_widget_state.is_search_enabled() { @@ -119,17 +120,11 @@ impl Painter { if is_sort_open { let processes_chunk = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Length(header_len + 4), Constraint::Min(0)]) + .constraints([Constraint::Length(SORT_MENU_WIDTH + 4), Constraint::Min(0)]) .split(proc_draw_loc); proc_draw_loc = processes_chunk[1]; - self.draw_process_sort( - f, - app_state, - processes_chunk[0], - draw_border, - widget_id + 2, - ); + self.draw_sort_table(f, app_state, processes_chunk[0], draw_border, widget_id + 2); } self.draw_processes_table(f, app_state, proc_draw_loc, draw_border, widget_id); @@ -148,18 +143,15 @@ impl Painter { if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) { let recalculate_column_widths = should_get_widget_bounds || proc_widget_state.requires_redraw; + + // Reset redraw marker. + // TODO: this should ideally be handled generically in the future. if proc_widget_state.requires_redraw { proc_widget_state.requires_redraw = false; } let is_on_widget = widget_id == app_state.current_widget.widget_id; - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)]) - .horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 }) - .direction(Direction::Horizontal) - .split(draw_loc)[0]; - - let (border_style, highlight_style) = if is_on_widget { + let (border_style, highlighted_text_style) = if is_on_widget { ( self.colours.highlighted_border_style, self.colours.currently_selected_text_style, @@ -168,355 +160,69 @@ impl Painter { (self.colours.border_style, self.colours.text_style) }; - let title_base = if app_state.app_config_fields.show_table_scroll_position { - if let Some(finalized_process_data) = app_state - .canvas_data - .finalized_process_data_map - .get(&widget_id) - { - let title = format!( - " Processes ({} of {}) ", - proc_widget_state - .scroll_state - .current_scroll_position - .saturating_add(1), - finalized_process_data.len() - ); - - if title.len() <= draw_loc.width.into() { - title - } else { - " Processes ".to_string() - } - } else { - " Processes ".to_string() - } - } else { - " Processes ".to_string() - }; - - let title = if app_state.is_expanded - && !proc_widget_state - .process_search_state - .search_state - .is_enabled - && !proc_widget_state.is_sort_open - { - const ESCAPE_ENDING: &str = "── Esc to go back "; - - let (chosen_title_base, expanded_title_base) = { - let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING); - - if temp_title_base.len() > draw_loc.width.into() { - ( - " Processes ".to_string(), - format!("{}{}", " Processes ", ESCAPE_ENDING), - ) - } else { - (title_base, temp_title_base) - } - }; - - Spans::from(vec![ - Span::styled(chosen_title_base, self.colours.widget_title_style), - Span::styled( - format!( - "─{}─ Esc to go back ", - "─".repeat( - usize::from(draw_loc.width).saturating_sub( - UnicodeSegmentation::graphemes( - expanded_title_base.as_str(), - true - ) - .count() - + 2 - ) - ) - ), - border_style, - ), - ]) - } else { - Spans::from(Span::styled(title_base, self.colours.widget_title_style)) - }; - - let process_block = if draw_border { - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(border_style) - } else if is_on_widget { - Block::default() - .borders(SIDE_BORDERS) - .border_style(self.colours.highlighted_border_style) - } else { - Block::default().borders(Borders::NONE) - }; - - if let Some(process_data) = &app_state - .canvas_data - .stringified_process_data_map - .get(&widget_id) - { - let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT { - 0 - } else { - app_state.app_config_fields.table_gap - }; - let position = get_start_position( - usize::from( - (draw_loc.height + (1 - table_gap)) - .saturating_sub(self.table_height_offset), - ), - &proc_widget_state.scroll_state.scroll_direction, - &mut proc_widget_state.scroll_state.scroll_bar, - proc_widget_state.scroll_state.current_scroll_position, - app_state.is_force_redraw, - ); - - // Sanity check - let start_position = if position >= process_data.len() { - process_data.len().saturating_sub(1) - } else { - position - }; - - let sliced_vec = &process_data[start_position..]; - let processed_sliced_vec = sliced_vec.iter().map(|(data, disabled)| { - ( - data.iter() - .map(|(entry, _alternative)| entry) - .collect::<Vec<_>>(), - disabled, - ) - }); - - let proc_table_state = &mut proc_widget_state.scroll_state.table_state; - proc_table_state.select(Some( - proc_widget_state - .scroll_state - .current_scroll_position - .saturating_sub(start_position), - )); - - // Draw! - let process_headers = proc_widget_state.columns.get_column_headers( - &proc_widget_state.process_sorting_type, - proc_widget_state.is_process_sort_descending, - ); - - // Calculate widths - // FIXME: See if we can move this into the recalculate block? I want to move column widths into the column widths - let hard_widths = if proc_widget_state.is_grouped { - PROCESS_HEADERS_HARD_WIDTH_GROUPED - } else { - PROCESS_HEADERS_HARD_WIDTH_NO_GROUP - }; - - if recalculate_column_widths { - let mut column_widths = process_headers - .iter() - .map(|entry| UnicodeWidthStr::width(entry.as_str()) as u16) - .collect::<Vec<_>>(); - - let soft_widths_min = column_widths - .iter() - .map(|width| Some(*width)) - .collect::<Vec<_>>(); - - proc_widget_state.table_width_state.desired_column_widths = { - for (row, _disabled) in processed_sliced_vec.clone() { - for (col, entry) in row.iter().enumerate() { - if let Some(col_width) = column_widths.get_mut(col) { - let grapheme_len = UnicodeWidthStr::width(entry.as_str()); - if grapheme_len as u16 > *col_width { - *col_width = grapheme_len as u16; - } - } - } - } - column_widths - }; - - proc_widget_state.table_width_state.desired_column_widths = proc_widget_state - .table_width_state - .desired_column_widths - .iter() - .zip(hard_widths) - .map(|(current, hard)| { - if let Some(hard) = hard { - if *hard > *current { - *hard - } else { - *current - } - } else { - *current - } - }) - .collect::<Vec<_>>(); - - let soft_widths_max = if proc_widget_state.is_grouped { - // Note grouped trees are not a thing. - - if proc_widget_state.is_using_command { - PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND - } else { - PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE - } - } else if proc_widget_state.is_using_command { - PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND - } else if proc_widget_state.is_tree_mode { - PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE - } else { - PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE - }; - - proc_widget_state.table_width_state.calculated_column_widths = - get_column_widths( - draw_loc.width, - hard_widths, - &soft_widths_min, - soft_widths_max, - &(proc_widget_state - |