summaryrefslogtreecommitdiffstats
path: root/src/canvas
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2022-05-07 03:38:55 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2022-05-15 21:02:28 -0400
commit7ee6f6a7373c915bde9e0c3860456b0aba6fdc8c (patch)
treee369edb0960029a9889b8c37d7aa3541705068bd /src/canvas
parent69ec526dc642ad5754d6ef8d073f85fc805ea00f (diff)
refactor: begin migration of process widget
Diffstat (limited to 'src/canvas')
-rw-r--r--src/canvas/components/text_table.rs206
-rw-r--r--src/canvas/dialogs/dd_dialog.rs10
-rw-r--r--src/canvas/drawing_utils.rs256
-rw-r--r--src/canvas/widgets/cpu_graph.rs4
-rw-r--r--src/canvas/widgets/process_table.rs703
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
-