diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2020-08-15 17:35:49 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-15 20:35:49 -0400 |
commit | f3897f0538f90c682b96bc340c3c05e80be10b2d (patch) | |
tree | f24673d4d5702e48b9d3d1889f7498c97ac238a1 /src/canvas | |
parent | 84f63f2f8306382dbf5cab819589161bf0b7c093 (diff) |
feature: Allow sorting by any column
This feature allows any column to be sortable.
This also adds:
- Inverting sort for current column with `I`
- Invoking a sort widget with `s` or `F6`. Close with same key or esc.
And:
- A bugfix in regards the basic menu and battery widget
- A lot of refactoring
Diffstat (limited to 'src/canvas')
-rw-r--r-- | src/canvas/dialogs/help_dialog.rs | 2 | ||||
-rw-r--r-- | src/canvas/drawing_utils.rs | 8 | ||||
-rw-r--r-- | src/canvas/widgets/basic_table_arrows.rs | 43 | ||||
-rw-r--r-- | src/canvas/widgets/cpu_graph.rs | 6 | ||||
-rw-r--r-- | src/canvas/widgets/process_table.rs | 226 |
5 files changed, 205 insertions, 80 deletions
diff --git a/src/canvas/dialogs/help_dialog.rs b/src/canvas/dialogs/help_dialog.rs index ae7d85b6..138622ce 100644 --- a/src/canvas/dialogs/help_dialog.rs +++ b/src/canvas/dialogs/help_dialog.rs @@ -72,7 +72,7 @@ impl HelpDialog for Painter { app_state.help_dialog_state.scroll_state.max_scroll_index = (self.styled_help_text.len() as u16 - + (constants::HELP_TEXT.len() as u16 - 3) + + (constants::HELP_TEXT.len() as u16 - 4) + overflow_buffer) .saturating_sub(draw_loc.height); diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs index 410b8916..283d8538 100644 --- a/src/canvas/drawing_utils.rs +++ b/src/canvas/drawing_utils.rs @@ -85,7 +85,7 @@ pub fn get_search_start_position( } match cursor_direction { - app::CursorDirection::RIGHT => { + app::CursorDirection::Right => { if current_cursor_position < *cursor_bar + num_columns { // If, using previous_scrolled_position, we can see the element // (so within that and + num_rows) just reuse the current previously scrolled position @@ -100,7 +100,7 @@ pub fn get_search_start_position( 0 } } - app::CursorDirection::LEFT => { + app::CursorDirection::Left => { if current_cursor_position <= *cursor_bar { // If it's past the first element, then show from that element downwards *cursor_bar = current_cursor_position; @@ -125,7 +125,7 @@ pub fn get_start_position( } match scroll_direction { - app::ScrollDirection::DOWN => { + 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 @@ -140,7 +140,7 @@ pub fn get_start_position( 0 } } - app::ScrollDirection::UP => { + 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; diff --git a/src/canvas/widgets/basic_table_arrows.rs b/src/canvas/widgets/basic_table_arrows.rs index 5e5c306c..18beab83 100644 --- a/src/canvas/widgets/basic_table_arrows.rs +++ b/src/canvas/widgets/basic_table_arrows.rs @@ -24,6 +24,15 @@ impl BasicTableArrows for Painter { fn draw_basic_table_arrows<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect, current_table: &BottomWidget, ) { + let current_table = if let BottomWidgetType::ProcSort = current_table.widget_type { + current_table + .right_neighbour + .map(|id| app_state.widget_map.get(&id).unwrap()) + .unwrap() + } else { + current_table + }; + // Effectively a paragraph with a ton of spacing let (left_table, right_table) = ( { @@ -33,7 +42,21 @@ impl BasicTableArrows for Painter { app_state .widget_map .get(&left_widget_id) - .map(|left_widget| &left_widget.widget_type) + .map(|left_widget| { + if left_widget.widget_type == BottomWidgetType::ProcSort { + left_widget + .left_neighbour + .map(|left_left_widget_id| { + app_state.widget_map.get(&left_left_widget_id).map( + |left_left_widget| &left_left_widget.widget_type, + ) + }) + .unwrap_or_else(|| Some(&BottomWidgetType::Temp)) + .unwrap_or_else(|| &BottomWidgetType::Temp) + } else { + &left_widget.widget_type + } + }) .unwrap_or_else(|| &BottomWidgetType::Temp) }) .unwrap_or_else(|| &BottomWidgetType::Temp) @@ -45,7 +68,23 @@ impl BasicTableArrows for Painter { app_state .widget_map .get(&right_widget_id) - .map(|right_widget| &right_widget.widget_type) + .map(|right_widget| { + if right_widget.widget_type == BottomWidgetType::ProcSort { + right_widget + .right_neighbour + .map(|right_right_widget_id| { + app_state.widget_map.get(&right_right_widget_id).map( + |right_right_widget| { + &right_right_widget.widget_type + }, + ) + }) + .unwrap_or_else(|| Some(&BottomWidgetType::Disk)) + .unwrap_or_else(|| &BottomWidgetType::Disk) + } else { + &right_widget.widget_type + } + }) .unwrap_or_else(|| &BottomWidgetType::Disk) }) .unwrap_or_else(|| &BottomWidgetType::Disk) diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 097d91b8..a01b7f5f 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use std::cmp::max; use crate::{ - app::App, + app::{layout_manager::WidgetDirection, App}, canvas::{ drawing_utils::{get_start_position, get_variable_intrinsic_widths}, Painter, @@ -57,9 +57,9 @@ impl CpuGraphWidget for Painter { // Skip drawing legend if app_state.current_widget.widget_id == (widget_id + 1) { if app_state.app_config_fields.left_legend { - app_state.move_widget_selection_right(); + app_state.move_widget_selection(&WidgetDirection::Right); } else { - app_state.move_widget_selection_left(); + app_state.move_widget_selection(&WidgetDirection::Left); } } self.draw_cpu_graph(f, app_state, draw_loc, widget_id); diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 2f041a16..54f0a5ad 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -1,5 +1,5 @@ use crate::{ - app::{self, App}, + app::App, canvas::{ drawing_utils::{ get_search_start_position, get_start_position, get_variable_intrinsic_widths, @@ -14,43 +14,68 @@ use tui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, terminal::Frame, text::{Span, Spans}, - widgets::{Block, Borders, Paragraph, Row, Table, Wrap}, + widgets::{Block, Borders, Paragraph, Row, Table}, }; use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation}; use unicode_width::UnicodeWidthStr; pub trait ProcessTableWidget { - fn draw_process_and_search<B: Backend>( + /// Draws and handles all process-related drawing. Use this. + /// - `widget_id` here represents the widget ID of the process widget itself! + fn draw_process_features<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, widget_id: u64, ); + /// Draws the process sort box. + /// - `widget_id` represents the widget ID of the process widget itself. + /// + /// This should not be directly called. fn draw_processes_table<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, widget_id: u64, ); + /// Draws the process sort box. + /// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget + /// state that is stored. + /// + /// This should not be directly called. fn draw_search_field<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, widget_id: u64, ); + + /// Draws the process sort box. + /// - `widget_id` represents the widget ID of the sort box itself --- NOT the process widget + /// state that is stored. + /// + /// This should not be directly called. + fn draw_process_sort<B: Backend>( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, + ); } impl ProcessTableWidget for Painter { - fn draw_process_and_search<B: Backend>( + fn draw_process_features<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; + + let mut proc_draw_loc = draw_loc; if process_widget_state.is_search_enabled() { let processes_chunk = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(search_height)].as_ref()) .split(draw_loc); + proc_draw_loc = processes_chunk[0]; - self.draw_processes_table(f, app_state, processes_chunk[0], draw_border, widget_id); self.draw_search_field( f, app_state, @@ -58,9 +83,25 @@ impl ProcessTableWidget for Painter { draw_border, widget_id + 1, ); - } else { - self.draw_processes_table(f, app_state, draw_loc, draw_border, widget_id); } + + if is_sort_open { + let processes_chunk = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(header_len + 4), Constraint::Min(0)].as_ref()) + .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_processes_table(f, app_state, proc_draw_loc, draw_border, widget_id); } } @@ -127,71 +168,17 @@ impl ProcessTableWidget for Painter { process.write_per_sec.to_string(), process.total_read.to_string(), process.total_write.to_string(), - process.process_states.to_string(), + process.process_state.to_string(), ] .into_iter(), ) }); - use app::data_harvester::processes::ProcessSorting; - let mut pid_or_count = if proc_widget_state.is_grouped { - "Count" - } else { - "PID(p)" - } - .to_string(); - let mut identifier = if proc_widget_state.is_using_full_path { - "Command(n)".to_string() - } else { - "Name(n)".to_string() - }; - let mut cpu = "CPU%(c)".to_string(); - let mut mem = "Mem%(m)".to_string(); - let rps = "R/s".to_string(); - let wps = "W/s".to_string(); - let total_read = "Read".to_string(); - let total_write = "Write".to_string(); - let process_state = "State ".to_string(); - - let direction_val = if proc_widget_state.process_sorting_reverse { - "▼".to_string() - } else { - "▲".to_string() - }; - - match proc_widget_state.process_sorting_type { - ProcessSorting::CPU => cpu += &direction_val, - ProcessSorting::MEM => mem += &direction_val, - ProcessSorting::PID => pid_or_count += &direction_val, - ProcessSorting::IDENTIFIER => identifier += &direction_val, - }; + let process_headers = proc_widget_state.columns.get_column_headers( + &proc_widget_state.process_sorting_type, + proc_widget_state.process_sorting_reverse, + ); - // TODO: Gonna have to figure out how to do left/right GUI notation. - let process_headers = if proc_widget_state.is_grouped { - vec![ - pid_or_count, - identifier, - cpu, - mem, - rps, - wps, - total_read, - total_write, - ] - } else { - vec![ - pid_or_count, - identifier, - cpu, - mem, - rps, - wps, - total_read, - total_write, - process_state, - ] - }; - proc_widget_state.num_columns = process_headers.len(); let process_headers_lens: Vec<usize> = process_headers .iter() .map(|entry| entry.len()) @@ -202,12 +189,12 @@ impl ProcessTableWidget for Painter { // TODO: This is a ugly work-around for now. let width_ratios = if proc_widget_state.is_grouped { - if proc_widget_state.is_using_full_path { + if proc_widget_state.is_using_command { vec![0.05, 0.7, 0.05, 0.05, 0.0375, 0.0375, 0.0375, 0.0375] } else { vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15] } - } else if proc_widget_state.is_using_full_path { + } else if proc_widget_state.is_using_command { vec![0.05, 0.7, 0.05, 0.05, 0.03, 0.03, 0.03, 0.03] } else { vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] @@ -235,6 +222,7 @@ impl ProcessTableWidget for Painter { .process_search_state .search_state .is_enabled + && !proc_widget_state.is_sort_open { const TITLE_BASE: &str = " Processes ── Esc to go back "; Span::styled( @@ -502,9 +490,107 @@ impl ProcessTableWidget for Painter { Paragraph::new(search_text) .block(process_search_block) .style(self.colours.text_style) - .alignment(Alignment::Left) - .wrap(Wrap { trim: false }), + .alignment(Alignment::Left), + margined_draw_loc[0], + ); + } + } + + fn draw_process_sort<B: Backend>( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, + ) { + let is_on_widget = widget_id == app_state.current_widget.widget_id; + + if let Some(proc_widget_state) = + app_state.proc_state.widget_states.get_mut(&(widget_id - 2)) + { + let current_scroll_position = proc_widget_state.columns.current_scroll_position; + let sort_string = proc_widget_state + .columns + .ordered_columns + .iter() + .filter(|column_type| { + proc_widget_state + .columns + .column_mapping + .get(&column_type) + .unwrap() + .enabled + }) + .enumerate() + .map(|(itx, column_type)| { + if current_scroll_position == itx { + ( + column_type.to_string(), + self.colours.currently_selected_text_style, + ) + } else { + (column_type.to_string(), self.colours.text_style) + } + }) + .collect::<Vec<_>>(); + + let position = get_start_position( + usize::from(draw_loc.height.saturating_sub(self.table_height_offset)), + &proc_widget_state.columns.scroll_direction, + &mut proc_widget_state.columns.previous_scroll_position, + current_scroll_position, + app_state.is_force_redraw, + ); + + // Sanity check + let start_position = if position >= sort_string.len() { + sort_string.len().saturating_sub(1) + } else { + position + }; + + let sliced_vec = &sort_string[start_position..]; + + let sort_options = sliced_vec + .iter() + .map(|(column, style)| Row::StyledData(vec![column].into_iter(), *style)); + + let column_state = &mut proc_widget_state.columns.column_state; + let current_border_style = if proc_widget_state + .process_search_state + .search_state + .is_invalid_search + { + self.colours.invalid_query_style + } else if is_on_widget { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }; + + let process_sort_block = if draw_border { + Block::default() + .borders(Borders::ALL) + .border_style(current_border_style) + } else if is_on_widget { + Block::default() + .borders(*SIDE_BORDERS) + .border_style(current_border_style) + } else { + Block::default().borders(Borders::NONE) + }; + + let margined_draw_loc = Layout::default() + .constraints([Constraint::Percentage(100)].as_ref()) + .horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 }) + .direction(Direction::Horizontal) + .split(draw_loc); + + f.render_stateful_widget( + Table::new(["Sort By"].iter(), sort_options) + .block(process_sort_block) + .header_style(self.colours.table_header_style) + .widths(&[Constraint::Percentage(100)]) + .header_gap(1), margined_draw_loc[0], + column_state, ); } } |