diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2022-10-12 16:25:38 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-12 16:25:38 -0400 |
commit | 2a740f48f79498e6a812670176c45c53e0383c05 (patch) | |
tree | f6c9c017477931ad5ff8a7624e21cc2dd7fade2b /src/app | |
parent | 1e5f0ea2d9dafa49279151b565154d9acf3b59d7 (diff) |
refactor: tables V2 (#749)
* refactor: move to new data table implementation
* more work towards refactor
* move disk and temp over, fix longstanding bug with disk and temp if removing the last value and selected
* work towards porting over CPU
work towards porting over CPU
fix typo
partially port over cpu, fix some potentially inefficient concat_string calls
more work towards cpu widget migration
some refactoring
* sortable data
sortable data
more refactoring
some sort refactoring
more refactoringgggg
column refactoring
renaming and reorganizing
more refactoring regarding column logic
add sort arrows again
* move over sort menu
* port over process
port over process
precommit
temp
temp two, remember to squash
work
fix broken ltr calculation and CPU hiding
add back row styling
temp
fix a bunch of issues, get proc working
more fixes around click
fix frozen issues
* fix dd process killing
* revert some of the persistent config changes from #257
* fix colouring for trees
* fix missing entries in tree
* keep columns if there is no data
* add and remove tests
* Fix ellipsis
Diffstat (limited to 'src/app')
-rw-r--r-- | src/app/data_farmer.rs | 45 | ||||
-rw-r--r-- | src/app/data_harvester/cpu.rs | 11 | ||||
-rw-r--r-- | src/app/data_harvester/cpu/heim.rs | 18 | ||||
-rw-r--r-- | src/app/data_harvester/processes/linux.rs | 1 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos/sysctl_bindings.rs | 6 | ||||
-rw-r--r-- | src/app/data_harvester/temperature.rs | 2 | ||||
-rw-r--r-- | src/app/frozen_state.rs | 44 | ||||
-rw-r--r-- | src/app/states.rs | 54 | ||||
-rw-r--r-- | src/app/widgets.rs | 17 | ||||
-rw-r--r-- | src/app/widgets/cpu_graph.rs | 175 | ||||
-rw-r--r-- | src/app/widgets/disk_table.rs | 147 | ||||
-rw-r--r-- | src/app/widgets/disk_table_widget.rs | 34 | ||||
-rw-r--r-- | src/app/widgets/process_table.rs | 812 | ||||
-rw-r--r-- | src/app/widgets/process_table/proc_widget_column.rs | 122 | ||||
-rw-r--r-- | src/app/widgets/process_table/proc_widget_data.rs | 274 | ||||
-rw-r--r-- | src/app/widgets/process_table/sort_table.rs | 42 | ||||
-rw-r--r-- | src/app/widgets/process_table_widget.rs | 1169 | ||||
-rw-r--r-- | src/app/widgets/temperature_table.rs | 101 | ||||
-rw-r--r-- | src/app/widgets/temperature_table_widget.rs | 29 |
19 files changed, 1756 insertions, 1347 deletions
diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index a6670712..e409957f 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -33,7 +33,7 @@ use regex::Regex; pub type TimeOffset = f64; pub type Value = f64; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct TimedData { pub rx_data: Value, pub tx_data: Value, @@ -45,19 +45,11 @@ pub struct TimedData { pub arc_data: Option<Value>, } -pub type StringPidMap = FxHashMap<String, Vec<Pid>>; - #[derive(Clone, Debug, Default)] pub struct ProcessData { /// A PID to process data map. pub process_harvest: FxHashMap<Pid, ProcessHarvest>, - /// A mapping from a process name to any PID with that name. - pub name_pid_map: StringPidMap, - - /// A mapping from a process command to any PID with that name. - pub cmd_pid_map: StringPidMap, - /// A mapping between a process PID to any children process PIDs. pub process_parent_mapping: FxHashMap<Pid, Vec<Pid>>, @@ -68,28 +60,10 @@ pub struct ProcessData { impl ProcessData { fn ingest(&mut self, list_of_processes: Vec<ProcessHarvest>) { // TODO: [Optimization] Probably more efficient to all of this in the data collection step, but it's fine for now. - self.name_pid_map.clear(); - self.cmd_pid_map.clear(); self.process_parent_mapping.clear(); // Reverse as otherwise the pid mappings are in the wrong order. list_of_processes.iter().rev().for_each(|process_harvest| { - if let Some(entry) = self.name_pid_map.get_mut(&process_harvest.name) { - entry.push(process_harvest.pid); - } else { - self.name_pid_map - .insert(process_harvest.name.to_string(), vec![process_harvest.pid]); - } - - if let Some(entry) = self.cmd_pid_map.get_mut(&process_harvest.command) { - entry.push(process_harvest.pid); - } else { - self.cmd_pid_map.insert( - process_harvest.command.to_string(), - vec![process_harvest.pid], - ); - } - if let Some(parent_pid) = process_harvest.parent_pid { if let Some(entry) = self.process_parent_mapping.get_mut(&parent_pid) { entry.push(process_harvest.pid); @@ -100,8 +74,6 @@ impl ProcessData { } }); - self.name_pid_map.shrink_to_fit(); - self.cmd_pid_map.shrink_to_fit(); self.process_parent_mapping.shrink_to_fit(); let process_pid_map = list_of_processes @@ -137,14 +109,14 @@ impl ProcessData { /// collected, and what is needed to convert into a displayable form. /// /// If the app is *frozen* - that is, we do not want to *display* any changing -/// data, keep updating this, don't convert to canvas displayable data! +/// data, keep updating this. As of 2021-09-08, we just clone the current collection +/// when it freezes to have a snapshot floating around. /// /// Note that with this method, the *app* thread is responsible for cleaning - /// not the data collector. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DataCollection { pub current_instant: Instant, - pub frozen_instant: Option<Instant>, pub timed_data_vec: Vec<(Instant, TimedData)>, pub network_harvest: network::NetworkHarvest, pub memory_harvest: memory::MemHarvest, @@ -167,7 +139,6 @@ impl Default for DataCollection { fn default() -> Self { DataCollection { current_instant: Instant::now(), - frozen_instant: None, timed_data_vec: Vec::default(), network_harvest: network::NetworkHarvest::default(), memory_harvest: memory::MemHarvest::default(), @@ -210,14 +181,6 @@ impl DataCollection { } } - pub fn freeze(&mut self) { - self.frozen_instant = Some(self.current_instant); - } - - pub fn thaw(&mut self) { - self.frozen_instant = None; - } - pub fn clean_data(&mut self, max_time_millis: u64) { let current_time = Instant::now(); diff --git a/src/app/data_harvester/cpu.rs b/src/app/data_harvester/cpu.rs index 23073606..c4131876 100644 --- a/src/app/data_harvester/cpu.rs +++ b/src/app/data_harvester/cpu.rs @@ -16,10 +16,15 @@ cfg_if::cfg_if! { pub type LoadAvgHarvest = [f32; 3]; -#[derive(Default, Debug, Clone)] +#[derive(Debug, Clone, Copy)] +pub enum CpuDataType { + Avg, + Cpu(usize), +} + +#[derive(Debug, Clone)] pub struct CpuData { - pub cpu_prefix: String, - pub cpu_count: Option<usize>, + pub data_type: CpuDataType, pub cpu_usage: f64, } diff --git a/src/app/data_harvester/cpu/heim.rs b/src/app/data_harvester/cpu/heim.rs index be2b251f..cdb6c8c2 100644 --- a/src/app/data_harvester/cpu/heim.rs +++ b/src/app/data_harvester/cpu/heim.rs @@ -18,7 +18,8 @@ cfg_if::cfg_if! { } } -use crate::data_harvester::cpu::{CpuData, CpuHarvest, PastCpuTotal, PastCpuWork}; +use crate::data_harvester::cpu::{CpuData, CpuDataType, CpuHarvest, PastCpuTotal, PastCpuWork}; + use futures::StreamExt; use std::collections::VecDeque; @@ -62,8 +63,7 @@ pub async fn get_cpu_data_list( let present_times = convert_cpu_times(&present); new_cpu_times.push(present_times); cpu_deque.push_back(CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: calculate_cpu_usage_percentage( convert_cpu_times(&past), present_times, @@ -72,8 +72,7 @@ pub async fn get_cpu_data_list( } else { new_cpu_times.push((0.0, 0.0)); cpu_deque.push_back(CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: 0.0, }); } @@ -96,8 +95,7 @@ pub async fn get_cpu_data_list( ( present_times, CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: calculate_cpu_usage_percentage( (*past_cpu_work, *past_cpu_total), present_times, @@ -108,8 +106,7 @@ pub async fn get_cpu_data_list( ( (*past_cpu_work, *past_cpu_total), CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: 0.0, }, ) @@ -147,8 +144,7 @@ pub async fn get_cpu_data_list( *previous_average_cpu_time = Some(new_average_cpu_time); cpu_deque.push_front(CpuData { - cpu_prefix: "AVG".to_string(), - cpu_count: None, + data_type: CpuDataType::Avg, cpu_usage, }) } diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index 99412247..a53a3e9a 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -116,7 +116,6 @@ fn get_linux_cpu_usage( } } -#[allow(clippy::too_many_arguments)] fn read_proc( prev_proc: &PrevProcDetails, stat: &Stat, cpu_usage: f64, cpu_fraction: f64, use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64, diff --git a/src/app/data_harvester/processes/macos/sysctl_bindings.rs b/src/app/data_harvester/processes/macos/sysctl_bindings.rs index 77b8901b..22bc07c6 100644 --- a/src/app/data_harvester/processes/macos/sysctl_bindings.rs +++ b/src/app/data_harvester/processes/macos/sysctl_bindings.rs @@ -60,16 +60,16 @@ pub(crate) struct extern_proc { /// Process identifier. pub p_pid: pid_t, - /// Save parent pid during ptrace. XXX + /// Save parent pid during ptrace. pub p_oppid: pid_t, - /// Sideways return value from fdopen. XXX + /// Sideways return value from fdopen. pub p_dupfd: i32, /// where user stack was allocated pub user_stack: caddr_t, - /// XXX Which thread is exiting? + /// Which thread is exiting? pub exit_thread: *mut c_void, /// allow to debug diff --git a/src/app/data_harvester/temperature.rs b/src/app/data_harvester/temperature.rs index 23465588..a9855f51 100644 --- a/src/app/data_harvester/temperature.rs +++ b/src/app/data_harvester/temperature.rs @@ -26,7 +26,7 @@ pub struct TempHarvest { pub temperature: f32, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum TemperatureType { Celsius, Kelvin, diff --git a/src/app/frozen_state.rs b/src/app/frozen_state.rs new file mode 100644 index 00000000..4fc7293d --- /dev/null +++ b/src/app/frozen_state.rs @@ -0,0 +1,44 @@ +use super::DataCollection; + +/// The [`FrozenState`] indicates whether the application state should be frozen. It is either not frozen or +/// frozen and containing a copy of the state at the time. +pub enum FrozenState { + NotFrozen, + Frozen(Box<DataCollection>), +} + +impl Default for FrozenState { + fn default() -> Self { + Self::NotFrozen + } +} + +pub type IsFrozen = bool; + +impl FrozenState { + /// Checks whether the [`FrozenState`] is currently frozen. + pub fn is_frozen(&self) -> IsFrozen { + matches!(self, FrozenState::Frozen(_)) + } + + /// Freezes the [`FrozenState`]. + pub fn freeze(&mut self, data: Box<DataCollection>) { + *self = FrozenState::Frozen(data); + } + + /// Unfreezes the [`FrozenState`]. + pub fn thaw(&mut self) { + *self = FrozenState::NotFrozen; + } + + /// Toggles the [`FrozenState`] and returns whether it is now frozen. + pub fn toggle(&mut self, data: &DataCollection) -> IsFrozen { + if self.is_frozen() { + self.thaw(); + false + } else { + self.freeze(Box::new(data.clone())); + true + } + } +} diff --git a/src/app/states.rs b/src/app/states.rs index 2ca18c0b..a1465640 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -4,11 +4,10 @@ use unicode_segmentation::GraphemeCursor; use crate::{ app::{layout_manager::BottomWidgetType, query::*}, - components::text_table::{CellContent, TableComponentColumn, TableComponentState, WidthBounds}, constants, }; -use super::widgets::{DiskWidgetState, ProcWidget, TempWidgetState}; +use super::widgets::{CpuWidgetState, DiskTableWidget, ProcWidget, TempWidgetState}; #[derive(Debug)] pub enum ScrollDirection { @@ -30,13 +29,6 @@ pub enum CursorDirection { Right, } -/// Meant for canvas operations involving table column widths. -#[derive(Default)] -pub struct CanvasTableWidthState { - pub desired_column_widths: Vec<u16>, - pub calculated_column_widths: Vec<u16>, -} - #[derive(PartialEq, Eq)] pub enum KillSignal { Cancel, @@ -184,42 +176,6 @@ impl NetState { } } -pub struct CpuWidgetState { - pub current_display_time: u64, - pub is_legend_hidden: bool, - pub autohide_timer: Option<Instant>, - pub table_state: TableComponentState, - pub is_multi_graph_mode: bool, -} - -impl CpuWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self { - const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"]; - const WIDTHS: [WidthBounds; CPU_LEGEND_HEADER.len()] = [ - WidthBounds::soft_from_str("CPU", Some(0.5)), - WidthBounds::soft_from_str("Use%", Some(0.5)), - ]; - - let table_state = TableComponentState::new( - CPU_LEGEND_HEADER - .iter() - .zip(WIDTHS) - .map(|(c, width)| { - TableComponentColumn::new_custom(CellContent::new(*c, None), width) - }) - .collect(), - ); - - CpuWidgetState { - current_display_time, - is_legend_hidden: false, - autohide_timer, - table_state, - is_multi_graph_mode: false, - } - } -} - pub struct CpuState { pub force_update: Option<u64>, pub widget_states: HashMap<u64, CpuWidgetState>, @@ -296,19 +252,19 @@ impl TempState { } pub struct DiskState { - pub widget_states: HashMap<u64, DiskWidgetState>, + pub widget_states: HashMap<u64, DiskTableWidget>, } impl DiskState { - pub fn init(widget_states: HashMap<u64, DiskWidgetState>) -> Self { + pub fn init(widget_states: HashMap<u64, DiskTableWidget>) -> Self { DiskState { widget_states } } - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> { + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskTableWidget> { self.widget_states.get_mut(&widget_id) } - pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> { + pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskTableWidget> { self.widget_states.get(&widget_id) } } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 99e9e210..6bdb0e55 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -1,8 +1,13 @@ -pub mod process_table_widget; -pub use process_table_widget::*; +// FIXME: Move this outside of app, along with components? -pub mod temperature_table_widget; -pub use temperature_table_widget::*; +pub mod process_table; +pub use process_table::*; -pub mod disk_table_widget; -pub use disk_table_widget::*; +pub mod temperature_table; +pub use temperature_table::*; + +pub mod disk_table; +pub use disk_table::*; + +pub mod cpu_graph; +pub use cpu_graph::*; diff --git a/src/app/widgets/cpu_graph.rs b/src/app/widgets/cpu_graph.rs new file mode 100644 index 00000000..4c127bb4 --- /dev/null +++ b/src/app/widgets/cpu_graph.rs @@ -0,0 +1,175 @@ +use std::{borrow::Cow, time::Instant}; + +use concat_string::concat_string; + +use tui::{style::Style, text::Text, widgets::Row}; + +use crate::{ + app::{data_harvester::cpu::CpuDataType, AppConfigFields}, + canvas::{canvas_colours::CanvasColours, Painter}, + components::data_table::{ + Column, ColumnHeader, DataTable, DataTableColumn, DataTableProps, DataTableStyling, + DataToCell, + }, + data_conversion::CpuWidgetData, + utils::gen_util::truncate_text, +}; + +#[derive(Default)] +pub struct CpuWidgetStyling { + pub all: Style, + pub avg: Style, + pub entries: Vec<Style>, +} + +impl CpuWidgetStyling { + fn from_colours(colours: &CanvasColours) -> Self { + let entries = if colours.cpu_colour_styles.is_empty() { + vec![Style::default()] + } else { + colours.cpu_colour_styles.clone() + }; + + Self { + all: colours.all_colour_style, + avg: colours.avg_colour_style, + entries, + } + } +} + +pub enum CpuWidgetColumn { + CPU, + Use, +} + +impl ColumnHeader for CpuWidgetColumn { + fn text(&self) -> Cow<'static, str> { + match self { + CpuWidgetColumn::CPU => "CPU".into(), + CpuWidgetColumn::Use => "Use%".into(), + } + } +} + +impl DataToCell<CpuWidgetColumn> for CpuWidgetData { + fn to_cell<'a>(&'a self, column: &CpuWidgetColumn, calculated_width: u16) -> Option<Text<'a>> { + const CPU_HIDE_BREAKPOINT: u16 = 5; + + // This is a bit of a hack, but apparently we can avoid having to do any fancy checks + // of showing the "All" on a specific column if the other is hidden by just always + // showing it on the CPU (first) column - if there isn't room for it, it will just collapse + // down. + // + // This is the same for the use percentages - we just *always* show them, and *always* hide the CPU column if + // it is too small. + match &self { + CpuWidgetData::All => match column { + CpuWidgetColumn::CPU => Some(truncate_text("All", calculated_width)), + CpuWidgetColumn::Use => None, + }, + CpuWidgetData::Entry { + data_type, + data: _, + last_entry, + } => match column { + CpuWidgetColumn::CPU => { + if calculated_width == 0 { + None + } else { + match data_type { + CpuDataType::Avg => Some(truncate_text("AVG", calculated_width)), + CpuDataType::Cpu(index) => { + let index_str = index.to_string(); + let text = if calculated_width < CPU_HIDE_BREAKPOINT { + truncate_text(&index_str, calculated_width) + } else { + truncate_text( + &concat_string!("CPU", index_str), + calculated_width, + ) + }; + + Some(text) + } + } + } + } + CpuWidgetColumn::Use => Some(truncate_text( + &format!("{:.0}%", last_entry.round()), + calculated_width, + )), + }, + } + } + + #[inline(always)] + fn style_row<'a>(&self, row: Row<'a>, painter: &Painter) -> Row<'a> { + let style = match self { + CpuWidgetData::All => painter.colours.all_colour_style, + CpuWidgetData::Entry { + data_type, + data: _, + last_entry: _, + } => match data_type { + CpuDataType::Avg => painter.colours.avg_colour_style, + CpuDataType::Cpu(index) => { + painter.colours.cpu_colour_styles + [index % painter.colours.cpu_colour_styles.len()] + } + }, + }; + + row.style(style) + } + + fn column_widths<C: DataTableColumn<CpuWidgetColumn>>( + _data: &[Self], _columns: &[C], + ) -> Vec<u16> + where + Self: Sized, + { + vec![1, 3] + } +} + +pub struct CpuWidgetState { + pub current_display_time: u64, + pub is_legend_hidden: bool, + pub show_avg: bool, + pub autohide_timer: Option<Instant>, + pub table: DataTable<CpuWidgetData, CpuWidgetColumn>, + pub styling: CpuWidgetStyling, +} + +impl CpuWidgetState { + pub fn new( + config: &AppConfigFields, current_display_time: u64, autohide_timer: Option<Instant>, + colours: &CanvasColours, + ) -> Self { + const COLUMNS: [Column<CpuWidgetColumn>; 2] = [ + Column::soft(CpuWidgetColumn::CPU, Some(0.5)), + Column::soft(CpuWidgetColumn::Use, Some(0.5)), + ]; + + let props = DataTableProps { + title: None, + table_gap: config.table_gap, + left_to_right: false, + is_basic: false, + show_table_scroll_position: false, // TODO: Should this be possible? + show_current_entry_when_unfocused: true, + }; + + let styling = DataTableStyling::from_colours(colours); + + CpuWidgetState { + current_display_time, + is_legend_hidden: false, + show_avg: config.show_average_cpu, + autohide_timer, + table: DataTable::new(COLUMNS, props, styling), + styling: CpuWidgetStyling::from_colours(colours), + } + } +} diff --git a/src/app/widgets/disk_table.rs b/src/app/widgets/disk_table.rs new file mode 100644 index 00000000..be444b45 --- /dev/null +++ b/src/app/widgets/disk_table.rs @@ -0,0 +1,147 @@ +use std::{borrow::Cow, cmp::max}; + +use kstring::KString; +use tui::text::Text; + +use crate::{ + app::AppConfigFields, + canvas::canvas_colours::CanvasColours, + components::data_table::{ + Column, ColumnHeader, DataTable, DataTableColumn, DataTableProps, DataTableStyling, + DataToCell, + }, + utils::gen_util::{get_decimal_bytes, truncate_text}, +}; + +#[derive(Clone)] +pub struct DiskWidgetData { + pub name: KString, + pub mount_point: KString, + pub free_bytes: Option<u64>, + pub used_bytes: Option<u64>, + pub total_bytes: Option<u64>, + pub io_read: KString, + pub io_write: KString, +} + +impl DiskWidgetData { + pub fn free_space(&self) -> KString { + if let Some(free_bytes) = self.free_bytes { + let converted_free_space = get_decimal_bytes(free_bytes); + format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into() + } else { + "N/A".into() + } + } + + pub fn total_space(&self) -> KString { + if let Some(total_bytes) = self.total_bytes { + let converted_total_space = get_decimal_bytes(total_bytes); + format!( + "{:.*}{}", + 0, converted_total_space.0, converted_total_space.1 + ) + .into() + } else { + "N/A".into() + } + } + + pub fn usage(&self) -> KString { + if let (Some(used_bytes), Some(total_bytes)) = (self.used_bytes, self.total_bytes) { + format!("{:.0}%", used_bytes as f64 / total_bytes as f64 * 100_f64).into() + } else { + "N/A".into() + } + } +} + +pub enum DiskWidgetColumn { + Disk, + Mount, + Used, + Free, + Total, + IoRead, + IoWrite, +} + +impl ColumnHeader for DiskWidgetColumn { + fn text(&self) -> Cow<'static, str> { + match self { + DiskWidgetColumn::Disk => "Disk", + DiskWidgetColumn::Mount => "Mount", + DiskWidgetColumn::Used => "Used", + DiskWidgetColumn::Free => "Free", + DiskWidgetColumn::Total => "Total", + DiskWidgetColumn::IoRead => "R/s", + DiskWidgetColumn::IoWrite => "W/s", + } + .into() + } +} + +impl DataToCell<DiskWidgetColumn> for DiskWidgetData { + fn to_cell<'a>(&'a self, column: &DiskWidgetColumn, calculated_width: u16) -> Option<Text<'a>> { + let text = match column { + DiskWidgetColumn::Disk => truncate_text(&self.name, calculated_width), + DiskWidgetColumn::Mount => truncate_text(&self.mount_point, calculated_width), + DiskWidgetColumn::Used => truncate_text(&self.usage(), calculated_width), + DiskWidgetColumn::Free => truncate_text(&self.free_space(), calculated_width), + DiskWidgetColumn::Total => truncate_text(&self.total_space(), calculated_width), + DiskWidgetColumn::IoRead => truncate_text(&self.io_read, calculated_width), + DiskWidgetColumn::IoWrite => truncate_text(&self.io_write, calculated_width), + }; + + Some(text) + } + + fn column_widths<C: DataTableColumn<DiskWidgetColumn>>( + data: &[Self], _columns: &[C], + ) -> Vec<u16> + where + Self: Sized, + { + let mut widths = vec![0; 7]; + + data.iter().for_each(|row| { + widths[0] = max(widths[0], row.name.len() as u16); + widths[1] = max(widths[1], row.mount_point.len() as u16); + }); + + widths + } +} + +pub struct DiskTableWidget { + pub table: DataTable<DiskWidgetData, DiskWidgetColumn>, +} + +impl DiskTableWidget { + pub fn new(config: &AppConfigFields, colours: &CanvasColours) -> Self { + const COLUMNS: [Column<DiskWidgetColumn>; 7] = [ + Column::soft(DiskWidgetColumn::Disk, Some(0.2)), + Column::soft(DiskWidgetColumn::Mount, Some(0.2)), + Column::hard(DiskWidgetColumn::Used, 4), + Column::hard(DiskWidgetColumn::Free, 6), + Column::hard(DiskWidgetColumn::Total, 6), + Column::hard(DiskWidgetColumn::IoRead, 7), + Column::hard(DiskWidgetColumn::IoWrite, 7), + ]; + + let props = DataTableProps { + title: Some(" Disks ".into()), + table_gap: config.table_gap, + left_to_right: true, + is_basic: config.use_basic_mode, + show_table_scroll_position: config.show_table_scroll_position, + show_current_entry_when_unfocused: false, + }; + + let styling = DataTableStyling::from_colours(colours); + + Self { + table: DataTable::new(COLUMNS, props, styling), + } + } +} diff --git a/src/app/widgets/disk_table_widget.rs b/src/app/widgets/disk_table_widget.rs deleted file mode 100644 index ecffb8cf..00000000 --- a/src/app/widgets/disk_table_widget.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::components::text_table::{ - CellContent, TableComponentColumn, TableComponentState, WidthBounds, -}; - -pub struct DiskWidgetState { - pub table_state: TableComponentState, -} - -impl Default for DiskWidgetState { - fn default() -> Self { - const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"]; - const WIDTHS: [WidthBounds; DISK_HEADERS.len()] = [ - WidthBounds::soft_from_str(DISK_HEADERS[0], Some(0.2)), - WidthBounds::soft_from_str(DISK_HEADERS[1], Some(0.2)), - WidthBounds::Hard(4), - WidthBounds::Hard(6), - WidthBounds::Hard(6), - WidthBounds::Hard(7), - WidthBounds::Hard(7), - ]; - - DiskWidgetState { - table_state: TableComponentState::new( - DISK_HEADERS - .iter() - .zip(WIDTHS) - .map(|(header, width)| { - TableComponentColumn::new_custom(CellContent::new(*header, None), width) - }) - .collect(), - ), - } - } -} diff --git a/src/app/widgets/process_table.rs b/src/app/widgets/process_table.rs new file mode 100644 index 00000000..967af03c --- /dev/null +++ b/src/app/widgets/process_table.rs @@ -0,0 +1,812 @@ +use std::{borrow::Cow, collections::hash_map::Entry}; + +use crate::{ + app::{ + data_farmer::{DataCollection, ProcessData}, + data_harvester::processes::ProcessHarvest, + query::*, + AppConfigFields, AppSearchState, + }, + canvas::canvas_colours::CanvasColours, + components::data_table::{ + Column, ColumnHeader, ColumnWidthBounds, DataTable, DataTableColumn, DataTableProps, + DataTableStyling, SortColumn, SortDataTable, SortDataTableProps, SortOrder, + }, + Pid, +}; + +use fxhash::{FxHashMap, FxHashSet}; +use itertools::Itertools; + +pub mod proc_widget_column; +pub use proc_widget_column::*; + +pub mod proc_widget_data; +pub use proc_widget_data::*; + +mod sort_table; +use sort_table::SortTableColumn; + +/// ProcessSearchState only deals with process' search's current settings and state. +pub struct ProcessSearchState { + pub search_state: AppSearchState, + pub is_ignoring_case: bool, + pub is_searching_whole_word: bool, + pub is_searching_with_regex: bool, |