summaryrefslogtreecommitdiffstats
path: root/src/app
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-08-30 01:30:21 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2021-09-05 19:09:03 -0400
commit27736b7fc048a9fbc8621d98ee99a4eb521a8c24 (patch)
tree2754efe73f1fd409e29d2b300c9e3ffec451444f /src/app
parent3fa50605b334909d8a0225eaefb6c91feb551ce5 (diff)
refactor: Add sort capabilities to processes
Diffstat (limited to 'src/app')
-rw-r--r--src/app/widgets/base.rs3
-rw-r--r--src/app/widgets/base/scrollable.rs14
-rw-r--r--src/app/widgets/base/sort_menu.rs76
-rw-r--r--src/app/widgets/base/sort_text_table.rs26
-rw-r--r--src/app/widgets/base/text_table.rs6
-rw-r--r--src/app/widgets/cpu.rs4
-rw-r--r--src/app/widgets/process.rs243
7 files changed, 267 insertions, 105 deletions
diff --git a/src/app/widgets/base.rs b/src/app/widgets/base.rs
index d32ea72e..c5bf80cf 100644
--- a/src/app/widgets/base.rs
+++ b/src/app/widgets/base.rs
@@ -17,3 +17,6 @@ pub use text_input::TextInput;
pub mod carousel;
pub use carousel::Carousel;
+
+pub mod sort_menu;
+pub use sort_menu::SortMenu;
diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs
index 54000a06..dc55a717 100644
--- a/src/app/widgets/base/scrollable.rs
+++ b/src/app/widgets/base/scrollable.rs
@@ -2,7 +2,7 @@ use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEve
use tui::{layout::Rect, widgets::TableState};
use crate::app::{
- event::{WidgetEventResult, MultiKey, MultiKeyResult},
+ event::{MultiKey, MultiKeyResult, WidgetEventResult},
Component,
};
@@ -110,7 +110,7 @@ impl Scrollable {
}
/// Update the index with this! This will automatically update the scroll direction as well!
- fn update_index(&mut self, new_index: usize) {
+ pub fn set_index(&mut self, new_index: usize) {
use std::cmp::Ordering;
match new_index.cmp(&self.current_index) {
@@ -130,7 +130,7 @@ impl Scrollable {
fn skip_to_first(&mut self) -> WidgetEventResult {
if self.current_index != 0 {
- self.update_index(0);
+ self.set_index(0);
WidgetEventResult::Redraw
} else {
@@ -141,7 +141,7 @@ impl Scrollable {
fn skip_to_last(&mut self) -> WidgetEventResult {
let last_index = self.num_items - 1;
if self.current_index != last_index {
- self.update_index(last_index);
+ self.set_index(last_index);
WidgetEventResult::Redraw
} else {
@@ -161,7 +161,7 @@ impl Scrollable {
} else if self.current_index == new_index {
WidgetEventResult::NoRedraw
} else {
- self.update_index(new_index);
+ self.set_index(new_index);
WidgetEventResult::Redraw
}
}
@@ -176,12 +176,12 @@ impl Scrollable {
if self.current_index == new_index {
WidgetEventResult::NoRedraw
} else {
- self.update_index(new_index);
+ self.set_index(new_index);
WidgetEventResult::Redraw
}
}
- pub fn update_num_items(&mut self, num_items: usize) {
+ pub fn set_num_items(&mut self, num_items: usize) {
self.num_items = num_items;
if num_items <= self.current_index {
diff --git a/src/app/widgets/base/sort_menu.rs b/src/app/widgets/base/sort_menu.rs
new file mode 100644
index 00000000..fc85d0bb
--- /dev/null
+++ b/src/app/widgets/base/sort_menu.rs
@@ -0,0 +1,76 @@
+use crossterm::event::{KeyEvent, MouseEvent};
+use tui::{backend::Backend, layout::Rect, widgets::Block, Frame};
+
+use crate::{
+ app::{event::WidgetEventResult, text_table::SimpleColumn, Component, TextTable},
+ canvas::Painter,
+};
+
+use super::sort_text_table::SortableColumn;
+
+/// A sortable, scrollable table with columns.
+pub struct SortMenu {
+ /// The underlying table.
+ table: TextTable,
+
+ /// The bounds.
+ bounds: Rect,
+}
+
+impl SortMenu {
+ /// Creates a new [`SortMenu`].
+ pub fn new(num_columns: usize) -> Self {
+ let sort_menu_columns = vec![SimpleColumn::new_hard("Sort By".into(), None)];
+ let mut sort_menu = TextTable::new(sort_menu_columns);
+ sort_menu.set_num_items(num_columns);
+
+ Self {
+ table: sort_menu,
+ bounds: Default::default(),
+ }
+ }
+
+ /// Updates the index of the [`SortMenu`].
+ pub fn set_index(&mut self, index: usize) {
+ self.table.scrollable.set_index(index);
+ }
+
+ /// Returns the current index of the [`SortMenu`].
+ pub fn current_index(&mut self) -> usize {
+ self.table.scrollable.current_index()
+ }
+
+ /// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`].
+ pub fn draw_sort_menu<B: Backend, C: SortableColumn>(
+ &mut self, painter: &Painter, f: &mut Frame<'_, B>, columns: &[C], block: Block<'_>,
+ block_area: Rect,
+ ) {
+ self.set_bounds(block_area);
+
+ let data = columns
+ .iter()
+ .map(|c| vec![(c.original_name().clone().into(), None, None)])
+ .collect::<Vec<_>>();
+
+ self.table
+ .draw_tui_table(painter, f, &data, block, block_area, true);
+ }
+}
+
+impl Component for SortMenu {
+ fn bounds(&self) -> Rect {
+ self.bounds
+ }
+
+ fn set_bounds(&mut self, new_bounds: Rect) {
+ self.bounds = new_bounds;
+ }
+
+ fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
+ self.table.handle_key_event(event)
+ }
+
+ fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult {
+ self.table.handle_mouse_event(event)
+ }
+}
diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs
index cc95f2c0..e8f0ff18 100644
--- a/src/app/widgets/base/sort_text_table.rs
+++ b/src/app/widgets/base/sort_text_table.rs
@@ -257,9 +257,6 @@ where
/// The underlying [`TextTable`].
pub table: TextTable<S>,
-
- /// A corresponding "sort" menu.
- pub sort_menu: TextTable,
}
impl<S> SortableTextTable<S>
@@ -268,15 +265,9 @@ where
{
/// Creates a new [`SortableTextTable`]. Note that `columns` cannot be empty.
pub fn new(columns: Vec<S>) -> Self {
- let sort_menu_columns = columns
- .iter()
- .map(|column| SimpleColumn::new_hard(column.original_name().clone(), None))
- .collect::<Vec<_>>();
-
let mut st = Self {
sort_index: 0,
table: TextTable::new(columns),
- sort_menu: TextTable::new(sort_menu_columns),
};
st.set_sort_index(0);
st
@@ -296,11 +287,16 @@ where
self.table.current_scroll_index()
}
- /// Returns the current column.
- pub fn current_column(&self) -> &S {
+ /// Returns the current column the table is sorting by.
+ pub fn current_sorting_column(&self) -> &S {
&self.table.columns[self.sort_index]
}
+ /// Returns the current column index the table is sorting by.
+ pub fn current_sorting_column_index(&self) -> usize {
+ self.sort_index
+ }
+
pub fn columns(&self) -> &[S] {
&self.table.columns
}
@@ -309,7 +305,7 @@ where
self.table.set_column(index, column)
}
- fn set_sort_index(&mut self, new_index: usize) {
+ pub fn set_sort_index(&mut self, new_index: usize) {
if new_index == self.sort_index {
if let Some(column) = self.table.columns.get_mut(self.sort_index) {
match column.sorting_status() {
@@ -356,12 +352,6 @@ where
self.table
.draw_tui_table(painter, f, data, block, block_area, show_selected_entry);
}
-
- /// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`].
- pub fn draw_sort_table<B: Backend>(
- &mut self, painter: &Painter, f: &mut Frame<'_, B>, block: Block<'_>, block_area: Rect,
- ) {
- }
}
impl<S> Component for SortableTextTable<S>
diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs
index 87f3f69d..3ce56886 100644
--- a/src/app/widgets/base/text_table.rs
+++ b/src/app/widgets/base/text_table.rs
@@ -173,6 +173,10 @@ where
self
}
+ pub fn columns(&self) -> &[C] {
+ &self.columns
+ }
+
fn displayed_column_names(&self) -> Vec<Cow<'static, str>> {
self.columns
.iter()
@@ -181,7 +185,7 @@ where
}
pub fn set_num_items(&mut self, num_items: usize) {
- self.scrollable.update_num_items(num_items);
+ self.scrollable.set_num_items(num_items);
}
pub fn set_column(&mut self, index: usize, column: C) {
diff --git a/src/app/widgets/cpu.rs b/src/app/widgets/cpu.rs
index 12647a47..e680aa00 100644
--- a/src/app/widgets/cpu.rs
+++ b/src/app/widgets/cpu.rs
@@ -174,16 +174,12 @@ impl Widget for CpuGraph {
}
};
- // debug!("Area: {:?}", area);
-
let split_area = Layout::default()
.margin(0)
.direction(Direction::Horizontal)
.constraints(constraints)
.split(area);
- // debug!("Split area: {:?}", split_area);
-
const Y_BOUNDS: [f64; 2] = [0.0, 100.5];
let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()];
diff --git a/src/app/widgets/process.rs b/src/app/widgets/process.rs
index 735616e1..c1b26c66 100644
--- a/src/app/widgets/process.rs
+++ b/src/app/widgets/process.rs
@@ -7,7 +7,7 @@ use unicode_segmentation::GraphemeCursor;
use tui::{
backend::Backend,
- layout::Rect,
+ layout::{Constraint, Direction, Layout, Rect},
widgets::{Block, Borders, TableState},
Frame,
};
@@ -15,7 +15,7 @@ use tui::{
use crate::{
app::{
data_harvester::processes::ProcessHarvest,
- event::{MultiKey, MultiKeyResult, WidgetEventResult},
+ event::{MultiKey, MultiKeyResult, ReturnSignal, WidgetEventResult},
query::*,
DataCollection,
},
@@ -30,7 +30,7 @@ use super::{
sort_text_table::{SimpleSortableColumn, SortStatus, SortableColumn},
text_table::TextTableData,
AppScrollWidgetState, CanvasTableWidthState, Component, CursorDirection, ScrollDirection,
- SortableTextTable, TextInput, TextTable, Widget,
+ SortMenu, SortableTextTable, TextInput, Widget,
};
/// AppSearchState deals with generic searching (I might do this in the future).
@@ -836,11 +836,17 @@ impl SortableColumn for ProcessSortColumn {
}
}
+enum ProcessSortState {
+ Shown,
+ Hidden,
+}
+
/// A searchable, sortable table to manage processes.
pub struct ProcessManager {
bounds: Rect,
process_table: SortableTextTable<ProcessSortColumn>,
- sort_table: TextTable,
+ sort_menu: SortMenu,
+
search_input: TextInput,
dd_multi: MultiKey,
@@ -848,7 +854,7 @@ pub struct ProcessManager {
selected: ProcessManagerSelection,
in_tree_mode: bool,
- show_sort: bool, // TODO: Add this for temp and disk???
+ sort_status: ProcessSortState,
show_search: bool,
search_modifiers: SearchModifiers,
@@ -873,17 +879,15 @@ impl ProcessManager {
ProcessSortColumn::new(ProcessSortType::State),
];
- let process_table = SortableTextTable::new(process_table_columns).default_sort_index(2);
-
let mut manager = Self {
bounds: Rect::default(),
- process_table,
- sort_table: TextTable::new(vec![]), // TODO: Do this too
+ sort_menu: SortMenu::new(process_table_columns.len()),
+ process_table: SortableTextTable::new(process_table_columns).default_sort_index(2),
search_input: TextInput::new(),
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
selected: ProcessManagerSelection::Processes,
in_tree_mode: false,
- show_sort: false,
+ sort_status: ProcessSortState::Hidden,
show_search: false,
search_modifiers: SearchModifiers::default(),
display_data: Default::default(),
@@ -911,7 +915,9 @@ impl ProcessManager {
if let ProcessManagerSelection::Sort = self.selected {
WidgetEventResult::NoRedraw
} else {
- self.show_sort = true;
+ self.sort_menu
+ .set_index(self.process_table.current_sorting_column_index());
+ self.sort_status = ProcessSortState::Shown;
self.selected = ProcessManagerSelection::Sort;
WidgetEventResult::Redraw
}
@@ -945,6 +951,20 @@ impl Component for ProcessManager {
}
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
+ // "Global" handling:
+ match event.code {
+ KeyCode::Esc => {
+ if let ProcessSortState::Shown = self.sort_status {
+ self.sort_status = ProcessSortState::Hidden;
+ if let ProcessManagerSelection::Sort = self.selected {
+ self.selected = ProcessManagerSelection::Processes;
+ }
+ return WidgetEventResult::Redraw;
+ }
+ }
+ _ => {}
+ }
+
match self.selected {
ProcessManagerSelection::Processes => {
// Try to catch some stuff first...
@@ -982,7 +1002,7 @@ impl Component for ProcessManager {
self.in_tree_mode = !self.in_tree_mode;
return WidgetEventResult::Redraw;
}
- KeyCode::F(6) => {
+ KeyCode::Char('s') | KeyCode::F(6) => {
return self.open_sort();
}
KeyCode::F(9) => {
@@ -1003,6 +1023,18 @@ impl Component for ProcessManager {
self.process_table.handle_key_event(event)
}
ProcessManagerSelection::Sort => {
+ match event.code {
+ KeyCode::Enter if event.modifiers.is_empty() => {
+ self.process_table
+ .set_sort_index(self.sort_menu.current_index());
+ return WidgetEventResult::Signal(ReturnSignal::Update);
+ }
+ _ => {}
+ }
+
+ self.sort_menu.handle_key_event(event)
+ }
+ ProcessManagerSelection::Search => {
if event.modifiers.is_empty() {
match event.code {
KeyCode::F(1) => {}
@@ -1019,9 +1051,8 @@ impl Component for ProcessManager {
}
}
- self.sort_table.handle_key_event(event)
+ self.search_input.handle_key_event(event)
}
- ProcessManagerSelection::Search => self.search_input.handle_key_event(event),
}
}
@@ -1041,12 +1072,12 @@ impl Component for ProcessManager {
WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s),
}
}
- } else if self.sort_table.does_border_intersect_mouse(&event) {
+ } else if self.sort_menu.does_border_intersect_mouse(&event) {
if let ProcessManagerSelection::Sort = self.selected {
- self.sort_table.handle_mouse_event(event)
+ self.sort_menu.handle_mouse_event(event)
} else {
self.selected = ProcessManagerSelection::Sort;
- self.sort_table.handle_mouse_event(event);
+ self.sort_menu.handle_mouse_event(event);
WidgetEventResult::Redraw
}
} else if self.search_input.does_border_intersect_mouse(&event) {
@@ -1063,7 +1094,7 @@ impl Component for ProcessManager {
}
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => match self.selected {
ProcessManagerSelection::Processes => self.process_table.handle_mouse_event(event),
- ProcessManagerSelection::Sort => self.sort_table.handle_mouse_event(event),
+ ProcessManagerSelection::Sort => self.sort_menu.handle_mouse_event(event),
ProcessManagerSelection::Search => self.search_input.handle_mouse_event(event),
},
_ => WidgetEventResult::NoRedraw,
@@ -1079,16 +1110,76 @@ impl Widget for ProcessManager {
fn draw<B: Backend>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
) {
- let block = Block::default()
- .border_style(if selected {
- painter.colours.highlighted_border_style
- } else {
- painter.colours.border_style
- })
- .borders(Borders::ALL);
-
- self.process_table
- .draw_tui_table(painter, f, &self.display_data, block, area, selected);
+ match self.sort_status {
+ ProcessSortState::Shown => {
+ const SORT_CONSTRAINTS: [Constraint; 2] =
+ [Constraint::Length(10), Constraint::Min(0)];
+
+ let split_area = Layout::default()
+ .margin(0)
+ .direction(Direction::Horizontal)
+ .constraints(SORT_CONSTRAINTS)
+ .split(area);
+
+ let sort_block = Block::default()
+ .border_style(if selected {
+ if let ProcessManagerSelection::Sort = self.selected {
+ painter.colours.highlighted_border_style
+ } else {
+ painter.colours.border_style
+ }
+ } else {
+ painter.colours.border_style
+ })
+ .borders(Borders::ALL);
+ self.sort_menu.draw_sort_menu(
+ painter,
+ f,
+ self.process_table.columns(),
+ sort_block,
+ split_area[0],
+ );
+
+ let process_block = Block::default()
+ .border_style(if selected {
+ if let ProcessManagerSelection::Processes = self.selected {
+ painter.colours.highlighted_border_style
+ } else {
+ painter.colours.border_style
+ }
+ } else {
+ painter.colours.border_style
+ })
+ .borders(Borders::ALL);
+
+ self.process_table.draw_tui_table(
+ painter,
+ f,
+ &self.display_data,
+ process_block,
+ split_area[1],
+ selected,
+ );
+ }
+ ProcessSortState::Hidden => {
+ let block = Block::default()
+ .border_style(if selected {
+ painter.colours.highlighted_border_style
+ } else {
+ painter.colours.border_style
+ })
+ .borders(Borders::ALL);
+
+ self.process_table.draw_tui_table(
+ painter,
+ f,
+ &self.display_data,
+ block,
+ area,
+ selected,
+ );
+ }
+ }
}
fn update_data(&mut self, data_collection: &DataCollection) {
@@ -1099,58 +1190,60 @@ impl Widget for ProcessManager {
// TODO: Filtering
true
})
- .sorted_by(match self.process_table.current_column().sort_type {
- ProcessSortType::Pid => {
- |a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid)
- }
- ProcessSortType::Count => {
- todo!()
- }
- ProcessSortType::Name => {
- |a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
- }
- ProcessSortType::Command => {
- |a: &&ProcessHarvest, b: &&ProcessHarvest| a.command.cmp(&b.command)
- }
- ProcessSortType::Cpu => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- FloatOrd(a.cpu_usage_percent).cmp(&FloatOrd(b.cpu_usage_percent))
- },
- ProcessSortType::Mem => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- a.mem_usage_bytes.cmp(&b.mem_usage_bytes)
- },
- ProcessSortType::MemPercent => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- FloatOrd(a.mem_usage_percent).cmp(&FloatOrd(b.mem_usage_percent))
- },
- ProcessSortType::Rps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- a.read_bytes_per_sec.cmp(&b.read_bytes_per_sec)
- },
- ProcessSortType::Wps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- a.write_bytes_per_sec.cmp(&b.write_bytes_per_sec)
- },
- ProcessSortType::TotalRead => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- a.total_read_bytes.cmp(&b.total_read_bytes)
- },
- ProcessSortType::TotalWrite => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
- a.total_write_bytes.cmp(&b.total_write_bytes)
- },
- ProcessSortType::User => {
- #[cfg(target_family = "unix")]
- {
- |a: &&ProcessHarvest, b: &&ProcessHarvest| a.user.cmp(&b.user)
+ .sorted_by(
+ match self.process_table.current_sorting_column().sort_type {
+ ProcessSortType::Pid => {
+ |a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid)
}
- #[cfg(not(target_family = "unix"))]
- {
- |_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
+ ProcessSortType::Count => {
+ todo!()
}
- }
- ProcessSortType::State => {
- |a: &&ProcessHarvest, b: &&ProcessHarvest| a.process_state.cmp(&b.process_state)
- }
- });
+ ProcessSortType::Name => {
+ |a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
+ }
+ ProcessSortType::Command => {
+ |a: &&ProcessHarvest, b: &&ProcessHarvest| a.command.cmp(&b.command)
+ }
+ ProcessSortType::Cpu => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ FloatOrd(a.cpu_usage_percent).cmp(&FloatOrd(b.cpu_usage_percent))
+ },
+ ProcessSortType::Mem => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ a.mem_usage_bytes.cmp(&b.mem_usage_bytes)
+ },
+ ProcessSortType::MemPercent => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ FloatOrd(a.mem_usage_percent).cmp(&FloatOrd(b.mem_usage_percent))
+ },
+ ProcessSortType::Rps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ a.read_bytes_per_sec.cmp(&b.read_bytes_per_sec)
+ },
+ ProcessSortType::Wps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ a.write_bytes_per_sec.cmp(&b.write_bytes_per_sec)
+ },
+ ProcessSortType::TotalRead => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ a.total_read_bytes.cmp(&b.total_read_bytes)
+ },
+ ProcessSortType::TotalWrite => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ a.total_write_bytes.cmp(&b.total_write_bytes)
+ },
+ ProcessSortType::User => {
+ #[cfg(target_family = "unix")]
+ {
+ |a: &&ProcessHarvest, b: &&ProcessHarvest| a.user.cmp(&b.user)
+ }
+ #[cfg(not(target_family = "unix"))]
+ {
+ |_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
+ }
+ }
+ ProcessSortType::State => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
+ a.process_state.cmp(&b.process_state)
+ },
+ },
+ );
self.display_data = if let SortStatus::SortDescending = self
.process_table
- .current_column()
+ .current_sorting_column()
.sortable_column
.sorting_status()
{