summaryrefslogtreecommitdiffstats
path: root/src/canvas
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2020-08-15 17:35:49 -0700
committerGitHub <noreply@github.com>2020-08-15 20:35:49 -0400
commitf3897f0538f90c682b96bc340c3c05e80be10b2d (patch)
treef24673d4d5702e48b9d3d1889f7498c97ac238a1 /src/canvas
parent84f63f2f8306382dbf5cab819589161bf0b7c093 (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.rs2
-rw-r--r--src/canvas/drawing_utils.rs8
-rw-r--r--src/canvas/widgets/basic_table_arrows.rs43
-rw-r--r--src/canvas/widgets/cpu_graph.rs6
-rw-r--r--src/canvas/widgets/process_table.rs226
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,
);
}
}