From 0b1d84fdf590bf8cacc5d0f94b67f63daa9b768a Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Wed, 1 Apr 2020 20:31:43 -0400 Subject: [PATCH] Add modularity to widget placement and inclusion (#95) --- .travis.yml | 2 +- Cargo.toml | 2 +- clippy.toml | 1 + docs/config.md | 40 +- src/app.rs | 3077 +++++++++++++--------- src/app/data_harvester/processes.rs | 13 - src/app/layout_manager.rs | 940 +++++++ src/app/process_killer.rs | 2 +- src/canvas.rs | 480 ++-- src/canvas/dialogs/dd_dialog.rs | 2 +- src/canvas/widgets/basic_table_arrows.rs | 34 +- src/canvas/widgets/cpu_basic.rs | 10 +- src/canvas/widgets/cpu_graph.rs | 474 ++-- src/canvas/widgets/disk_table.rs | 194 +- src/canvas/widgets/mem_basic.rs | 8 +- src/canvas/widgets/mem_graph.rs | 183 +- src/canvas/widgets/network_basic.rs | 12 +- src/canvas/widgets/network_graph.rs | 238 +- src/canvas/widgets/process_table.rs | 645 ++--- src/canvas/widgets/temp_table.rs | 192 +- src/constants.rs | 41 +- src/data_conversion.rs | 54 +- src/main.rs | 267 +- src/options.rs | 315 ++- src/options/layout_manager.rs | 3 - src/options/layout_options.rs | 307 +++ tests/arg_tests.rs | 22 +- 27 files changed, 4988 insertions(+), 2570 deletions(-) create mode 100644 clippy.toml create mode 100644 src/app/layout_manager.rs delete mode 100644 src/options/layout_manager.rs create mode 100644 src/options/layout_options.rs diff --git a/.travis.yml b/.travis.yml index fed4dac1..38288797 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ os: jobs: allow_failures: - rust: nightly - - env: TARGET=x86_64-pc-windows-gnu # Seems to cause problems. TODO: Add test for it, but keep allow fail. + - env: TARGET=x86_64-pc-windows-gnu # Seems to cause problems. fast_finish: true branches: only: diff --git a/Cargo.toml b/Cargo.toml index 46129eba..f6cb3147 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ fern = "0.6.0" futures = "0.3.4" heim = "0.0.10" log = "0.4.8" -regex = "1.3.4" +regex = "1.3" sysinfo = "0.11" toml = "0.5.6" tui = {version = "0.8", features = ["crossterm"], default-features = false } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..59155b42 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +cognitive-complexity-threshold = 35 \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index e18db307..d625739a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -8,7 +8,6 @@ One use of a config file is to set boot flags to execute without having to state - These options are generally the same as the long names as other flags (ex: `case_sensitive = true`). - Note that if a flag and an option conflict, the flag has higher precedence (ex: if the `-c` and `temperature_type = kelvin` both exist, the Celsius temperature type is ultimately chosen). - For temperature type, use `temperature_type = "kelvin|k|celsius|c|fahrenheit|f"`. -- For default widgets, use `default_widget = "cpu_default|memory_default|disk_default|temperature_default|network_default|process_default"`. ## Colours @@ -36,6 +35,45 @@ Supported named colours are one of the following: `Reset, Black, Red, Green, Yel Note some colours may not be compatible with the terminal you are using. For example, macOS's default Terminal does not play nice with many colours. +## Layout + +As of 0.3.0, bottom supports custom layouts. Layouts are in the TOML specification, and are arranged by row -> column -> row. For example, the default layout: + +```toml +[[row]] + ratio=30 + [[row.child]] + type="cpu" +[[row]] + ratio=40 + [[row.child]] + ratio=4 + type="mem" + [[row.child]] + ratio=3 + [[row.child.child]] + type="temp" + [[row.child.child]] + type="disk" +[[row]] + ratio=30 + [[row.child]] + type="net" + [[row.child]] + type="proc" + default=true +``` + +Valid types are: + +- `cpu` +- `mem` +- `proc` +- `net` +- `temp` +- `disk` +- `empty` + ## Default config locations bottom will check specific locations by default for a config file. If no file is found, it will be created. diff --git a/src/app.rs b/src/app.rs index 4cca1d81..ba2186a4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,4 @@ -use std::cmp::max; -use std::time::Instant; +use std::{cmp::max, collections::HashMap, time::Instant}; use unicode_segmentation::GraphemeCursor; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; @@ -8,64 +7,20 @@ use typed_builder::*; use data_farmer::*; use data_harvester::{processes, temperature}; +use layout_manager::*; -use crate::{canvas, constants, utils::error::Result}; +use crate::{ + canvas, constants, + utils::error::{BottomError, Result}, +}; pub mod data_farmer; pub mod data_harvester; +pub mod layout_manager; mod process_killer; const MAX_SEARCH_LENGTH: usize = 200; -#[derive(Debug, Clone, Copy)] -pub enum WidgetPosition { - Cpu, - CpuLegend, - Mem, - Disk, - Temp, - Network, - NetworkLegend, - Process, - ProcessSearch, - BasicCpu, - BasicMem, - BasicNet, -} - -impl WidgetPosition { - pub fn is_widget_table(self) -> bool { - match self { - WidgetPosition::Disk - | WidgetPosition::Process - | WidgetPosition::ProcessSearch - | WidgetPosition::Temp - | WidgetPosition::CpuLegend => true, - _ => false, - } - } - - pub fn is_widget_graph(self) -> bool { - match self { - WidgetPosition::Cpu | WidgetPosition::Network | WidgetPosition::Mem => true, - _ => false, - } - } - - pub fn get_pretty_name(self) -> String { - use WidgetPosition::*; - match self { - Cpu | BasicCpu | CpuLegend => "CPU", - Mem | BasicMem => "Memory", - Disk => "Disks", - Temp => "Temperature", - Network | BasicNet | NetworkLegend => "Network", - Process | ProcessSearch => "Processes", - } - .to_string() - } -} - #[derive(Debug)] pub enum ScrollDirection { // UP means scrolling up --- this usually DECREMENTS @@ -74,6 +29,12 @@ pub enum ScrollDirection { DOWN, } +impl Default for ScrollDirection { + fn default() -> Self { + ScrollDirection::DOWN + } +} + #[derive(Debug)] pub enum CursorDirection { LEFT, @@ -85,28 +46,52 @@ pub enum CursorDirection { pub struct AppScrollWidgetState { pub current_scroll_position: u64, pub previous_scroll_position: u64, -} - -pub struct AppScrollState { pub scroll_direction: ScrollDirection, - pub process_scroll_state: AppScrollWidgetState, - pub disk_scroll_state: AppScrollWidgetState, - pub temp_scroll_state: AppScrollWidgetState, - pub cpu_scroll_state: AppScrollWidgetState, } -impl Default for AppScrollState { +#[derive(Default)] +pub struct AppDeleteDialogState { + pub is_showing_dd: bool, + pub is_on_yes: bool, // Defaults to "No" +} + +pub enum AppHelpCategory { + General, + Process, + Search, +} + +pub struct AppHelpDialogState { + pub is_showing_help: bool, + pub current_category: AppHelpCategory, +} + +impl Default for AppHelpDialogState { fn default() -> Self { - AppScrollState { - scroll_direction: ScrollDirection::DOWN, - process_scroll_state: AppScrollWidgetState::default(), - disk_scroll_state: AppScrollWidgetState::default(), - temp_scroll_state: AppScrollWidgetState::default(), - cpu_scroll_state: AppScrollWidgetState::default(), + AppHelpDialogState { + is_showing_help: false, + current_category: AppHelpCategory::General, } } } +/// AppConfigFields is meant to cover basic fields that would normally be set +/// by config files or launch options. +pub struct AppConfigFields { + pub update_rate_in_milliseconds: u64, + pub temperature_type: temperature::TemperatureType, + pub use_dot: bool, + pub left_legend: bool, + pub show_average_cpu: bool, + pub use_current_cpu_total: bool, + pub show_disabled_data: bool, + pub use_basic_mode: bool, + pub default_time_value: u64, + pub time_interval: u64, + pub hide_time: bool, + pub autohide_time: bool, +} + /// AppSearchState deals with generic searching (I might do this in the future). pub struct AppSearchState { pub is_enabled: bool, @@ -186,437 +171,58 @@ impl ProcessSearchState { } } -#[derive(Default)] -pub struct AppDeleteDialogState { - pub is_showing_dd: bool, - pub is_on_yes: bool, // Defaults to "No" -} - -pub enum AppHelpCategory { - General, - Process, - Search, -} - -pub struct AppHelpDialogState { - pub is_showing_help: bool, - pub current_category: AppHelpCategory, -} - -impl Default for AppHelpDialogState { - fn default() -> Self { - AppHelpDialogState { - is_showing_help: false, - current_category: AppHelpCategory::General, - } - } -} - -/// AppConfigFields is meant to cover basic fields that would normally be set -/// by config files or launch options. -pub struct AppConfigFields { - pub update_rate_in_milliseconds: u64, - pub temperature_type: temperature::TemperatureType, - pub use_dot: bool, - pub left_legend: bool, - pub show_average_cpu: bool, - pub use_current_cpu_total: bool, - pub show_disabled_data: bool, - pub use_basic_mode: bool, - pub default_time_value: u64, - pub time_interval: u64, - pub hide_time: bool, - pub autohide_time: bool, -} - -/// Network specific -pub struct NetState { - pub is_showing_tray: bool, - pub is_showing_rx: bool, - pub is_showing_tx: bool, - pub zoom_level: f64, - pub current_display_time: u64, - pub force_update: bool, - pub autohide_timer: Option, -} - -impl NetState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - NetState { - is_showing_tray: false, - is_showing_rx: true, - is_showing_tx: true, - zoom_level: 100.0, - current_display_time, - force_update: false, - autohide_timer, - } - } -} - -/// CPU specific -pub struct CpuState { - pub is_showing_tray: bool, - pub zoom_level: f64, - pub core_show_vec: Vec, - pub num_cpus_shown: u64, - pub current_display_time: u64, - pub force_update: bool, - pub autohide_timer: Option, -} - -impl CpuState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - CpuState { - is_showing_tray: false, - zoom_level: 100.0, - core_show_vec: Vec::new(), - num_cpus_shown: 0, - current_display_time, - force_update: false, - autohide_timer, - } - } -} - -/// Memory specific -pub struct MemState { - pub is_showing_tray: bool, - pub is_showing_ram: bool, - pub is_showing_swap: bool, - pub zoom_level: f64, - pub current_display_time: u64, - pub force_update: bool, - pub autohide_timer: Option, -} - -impl MemState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - MemState { - is_showing_tray: false, - is_showing_ram: true, - is_showing_swap: true, - zoom_level: 100.0, - current_display_time, - force_update: false, - autohide_timer, - } - } -} - -#[derive(TypedBuilder)] -pub struct App { - #[builder(default=processes::ProcessSorting::CPU, setter(skip))] - pub process_sorting_type: processes::ProcessSorting, - - #[builder(default = true, setter(skip))] - pub process_sorting_reverse: bool, - - #[builder(default = false, setter(skip))] - pub force_update_processes: bool, - - #[builder(default, setter(skip))] - pub app_scroll_positions: AppScrollState, - - #[builder(default = false, setter(skip))] - awaiting_second_char: bool, - - #[builder(default, setter(skip))] - second_char: Option, - - #[builder(default, setter(skip))] - pub dd_err: Option, - - #[builder(default, setter(skip))] - to_delete_process_list: Option<(String, Vec)>, - - #[builder(default = false, setter(skip))] - pub is_frozen: bool, - - #[builder(default = Instant::now(), setter(skip))] - last_key_press: Instant, - - #[builder(default, setter(skip))] - pub canvas_data: canvas::DisplayableData, - - #[builder(default = false)] - enable_grouping: bool, - - #[builder(default, setter(skip))] - pub data_collection: DataCollection, - - #[builder(default, setter(skip))] +pub struct ProcWidgetState { pub process_search_state: ProcessSearchState, - - #[builder(default, setter(skip))] - pub delete_dialog_state: AppDeleteDialogState, - - #[builder(default, setter(skip))] - pub help_dialog_state: AppHelpDialogState, - - #[builder(default = false, setter(skip))] - pub is_expanded: bool, - - #[builder(default = false, setter(skip))] - pub is_resized: bool, - - pub cpu_state: CpuState, - pub mem_state: MemState, - pub net_state: NetState, - - pub app_config_fields: AppConfigFields, - pub current_widget_selected: WidgetPosition, - pub previous_basic_table_selected: WidgetPosition, + pub is_grouped: bool, + pub scroll_state: AppScrollWidgetState, + pub process_sorting_type: processes::ProcessSorting, + pub process_sorting_reverse: bool, } -impl App { - pub fn reset(&mut self) { - // Reset multi - self.reset_multi_tap_keys(); +impl ProcWidgetState { + pub fn init( + is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool, + ) -> Self { + let mut process_search_state = ProcessSearchState::default(); + if is_case_sensitive { + // By default it's off + process_search_state.search_toggle_ignore_case(); + } + if is_match_whole_word { + process_search_state.search_toggle_whole_word(); + } + if is_use_regex { + process_search_state.search_toggle_regex(); + } - // Reset dialog state - self.help_dialog_state.is_showing_help = false; - self.delete_dialog_state.is_showing_dd = false; - - // Close search and reset it - self.process_search_state.search_state.reset(); - self.force_update_processes = true; - - // Clear current delete list - self.to_delete_process_list = None; - self.dd_err = None; - - // Unfreeze. - self.is_frozen = false; - - // Reset zoom - self.reset_cpu_zoom(); - self.reset_mem_zoom(); - self.reset_net_zoom(); - - // Reset data - self.data_collection.reset(); - } - - pub fn on_esc(&mut self) { - self.reset_multi_tap_keys(); - if self.is_in_dialog() { - self.help_dialog_state.is_showing_help = false; - self.help_dialog_state.current_category = AppHelpCategory::General; - self.delete_dialog_state.is_showing_dd = false; - self.delete_dialog_state.is_on_yes = false; - self.to_delete_process_list = None; - self.dd_err = None; - } else if self.is_filtering_or_searching() { - match self.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::CpuLegend => { - self.cpu_state.is_showing_tray = false; - if self - .app_scroll_positions - .cpu_scroll_state - .current_scroll_position - >= self.cpu_state.num_cpus_shown - { - let new_position = max(0, self.cpu_state.num_cpus_shown as i64 - 1) as u64; - self.app_scroll_positions - .cpu_scroll_state - .current_scroll_position = new_position; - self.app_scroll_positions - .cpu_scroll_state - .previous_scroll_position = 0; - } - } - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - if self.process_search_state.search_state.is_enabled { - self.current_widget_selected = WidgetPosition::Process; - self.process_search_state.search_state.is_enabled = false; - } - } - WidgetPosition::Mem => { - self.mem_state.is_showing_tray = false; - } - WidgetPosition::Network => { - self.net_state.is_showing_tray = false; - } - _ => {} - } - } else if self.is_expanded { - self.is_expanded = false; - self.is_resized = true; - if self.app_config_fields.use_basic_mode { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::CpuLegend => WidgetPosition::BasicCpu, - WidgetPosition::Mem => WidgetPosition::BasicMem, - WidgetPosition::Network => WidgetPosition::BasicNet, - _ => self.current_widget_selected, - } - } + ProcWidgetState { + process_search_state, + is_grouped, + scroll_state: AppScrollWidgetState::default(), + process_sorting_type: processes::ProcessSorting::CPU, + process_sorting_reverse: true, } } - fn is_filtering_or_searching(&self) -> bool { - match self.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::CpuLegend => self.cpu_state.is_showing_tray, - // WidgetPosition::Mem => self.mem_state.is_showing_tray, - // WidgetPosition::Network => self.net_state.is_showing_tray, - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - self.process_search_state.search_state.is_enabled - } - _ => false, - } + pub fn get_cursor_position(&self) -> usize { + self.process_search_state + .search_state + .grapheme_cursor + .cur_cursor() } - fn reset_multi_tap_keys(&mut self) { - self.awaiting_second_char = false; - self.second_char = None; + pub fn get_char_cursor_position(&self) -> usize { + self.process_search_state.search_state.char_cursor_position } - fn is_in_dialog(&self) -> bool { - self.help_dialog_state.is_showing_help || self.delete_dialog_state.is_showing_dd - } - - pub fn toggle_grouping(&mut self) { - // Disallow usage whilst in a dialog and only in processes - if !self.is_in_dialog() { - if let WidgetPosition::Process = self.current_widget_selected { - self.enable_grouping = !(self.enable_grouping); - self.force_update_processes = true; - } - } - } - - pub fn on_tab(&mut self) { - match self.current_widget_selected { - WidgetPosition::Process => { - self.toggle_grouping(); - if self.is_grouped() { - self.search_with_name(); - } else { - self.force_update_processes = true; - } - } - WidgetPosition::ProcessSearch => { - if !self.is_grouped() { - if self.process_search_state.is_searching_with_pid { - self.search_with_name(); - } else { - self.search_with_pid(); - } - } - } - _ => {} - } - } - - pub fn is_grouped(&self) -> bool { - self.enable_grouping - } - - pub fn on_space(&mut self) { - match self.current_widget_selected { - WidgetPosition::CpuLegend => { - let curr_posn = self - .app_scroll_positions - .cpu_scroll_state - .current_scroll_position; - if self.cpu_state.is_showing_tray - && curr_posn < self.data_collection.cpu_harvest.len() as u64 - { - self.cpu_state.core_show_vec[curr_posn as usize] = - !self.cpu_state.core_show_vec[curr_posn as usize]; - - if !self.app_config_fields.show_disabled_data { - if !self.cpu_state.core_show_vec[curr_posn as usize] { - self.cpu_state.num_cpus_shown -= 1; - } else { - self.cpu_state.num_cpus_shown += 1; - } - } - } - } - WidgetPosition::Network => {} - _ => {} - } - } - - pub fn on_slash(&mut self) { - if !self.is_in_dialog() { - match self.current_widget_selected { - WidgetPosition::BasicCpu if self.is_expanded => { - self.current_widget_selected = WidgetPosition::Cpu; - self.cpu_state.is_showing_tray = true; - } - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - // Toggle on - self.process_search_state.search_state.is_enabled = true; - self.current_widget_selected = WidgetPosition::ProcessSearch; - if self.is_grouped() { - self.search_with_name(); - } - } - WidgetPosition::Cpu | WidgetPosition::CpuLegend => { - self.cpu_state.is_showing_tray = true; - self.current_widget_selected = WidgetPosition::CpuLegend - } - // WidgetPosition::Mem => { - // self.mem_state.is_showing_tray = true; - // } - // WidgetPosition::Network => { - // self.net_state.is_showing_tray = true; - // } - _ => {} - } - } - } - - pub fn is_searching(&self) -> bool { + pub fn is_search_enabled(&self) -> bool { self.process_search_state.search_state.is_enabled } - pub fn is_in_search_widget(&self) -> bool { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - true - } else { - false - } - } - - pub fn search_with_pid(&mut self) { - if !self.is_in_dialog() && self.is_searching() { - self.process_search_state.is_searching_with_pid = true; - self.force_update_processes = true; - } - } - - pub fn search_with_name(&mut self) { - if !self.is_in_dialog() && self.is_searching() { - self.process_search_state.is_searching_with_pid = false; - self.force_update_processes = true; - } - } - pub fn get_current_search_query(&self) -> &String { &self.process_search_state.search_state.current_search_query } - pub fn toggle_ignore_case(&mut self) { - self.process_search_state.search_toggle_ignore_case(); - self.update_regex(); - self.force_update_processes = true; - } - - pub fn toggle_search_whole_word(&mut self) { - self.process_search_state.search_toggle_whole_word(); - self.update_regex(); - self.force_update_processes = true; - } - - pub fn toggle_search_regex(&mut self) { - self.process_search_state.search_toggle_regex(); - self.update_regex(); - self.force_update_processes = true; - } - pub fn update_regex(&mut self) { if self .process_search_state @@ -660,114 +266,12 @@ impl App { self.process_search_state.search_state.current_regex = Some(new_regex); } - self.app_scroll_positions - .process_scroll_state - .previous_scroll_position = 0; - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = 0; - } - - pub fn get_cursor_position(&self) -> usize { - self.process_search_state - .search_state - .grapheme_cursor - .cur_cursor() - } - - pub fn get_char_cursor_position(&self) -> usize { - self.process_search_state.search_state.char_cursor_position - } - - /// One of two functions allowed to run while in a dialog... - pub fn on_enter(&mut self) { - if self.delete_dialog_state.is_showing_dd { - if self.delete_dialog_state.is_on_yes { - // If within dd... - if self.dd_err.is_none() { - // Also ensure that we didn't just fail a dd... - let dd_result = self.kill_highlighted_process(); - self.delete_dialog_state.is_on_yes = false; - - // Check if there was an issue... if so, inform the user. - if let Err(dd_err) = dd_result { - self.dd_err = Some(dd_err.to_string()); - } else { - self.delete_dialog_state.is_showing_dd = false; - } - } - } else { - self.delete_dialog_state.is_showing_dd = false; - } - } else if !self.is_in_dialog() { - // Pop-out mode. We ignore if in process search. - - match self.current_widget_selected { - WidgetPosition::ProcessSearch => {} - _ => { - self.is_expanded = true; - self.is_resized = true; - } - } - - if self.app_config_fields.use_basic_mode { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::BasicCpu => WidgetPosition::Cpu, - WidgetPosition::BasicMem => WidgetPosition::Mem, - WidgetPosition::BasicNet => WidgetPosition::Network, - _ => self.current_widget_selected, - } - } - } - } - - pub fn on_delete(&mut self) { - match self.current_widget_selected { - WidgetPosition::Process => self.start_dd(), - WidgetPosition::ProcessSearch => { - if self.process_search_state.search_state.is_enabled - && self.get_cursor_position() - < self - .process_search_state - .search_state - .current_search_query - .len() - { - self.process_search_state - .search_state - .current_search_query - .remove(self.get_cursor_position()); - - self.process_search_state.search_state.grapheme_cursor = GraphemeCursor::new( - self.get_cursor_position(), - self.process_search_state - .search_state - .current_search_query - .len(), - true, - ); - - self.update_regex(); - self.force_update_processes = true; - } - } - _ => {} - } - } - - /// Deletes an entire word till the next space or end - #[allow(unused_variables)] - pub fn skip_word_backspace(&mut self) { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - if self.process_search_state.search_state.is_enabled {} - } + self.scroll_state.previous_scroll_position = 0; + self.scroll_state.current_scroll_position = 0; } pub fn clear_search(&mut self) { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - self.force_update_processes = true; - self.process_search_state.search_state.reset(); - } + self.process_search_state.search_state.reset(); } pub fn search_walk_forward(&mut self, start_position: usize) { @@ -791,72 +295,815 @@ impl App { ) .unwrap(); } +} - pub fn on_backspace(&mut self) { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - if self.process_search_state.search_state.is_enabled && self.get_cursor_position() > 0 { - self.search_walk_back(self.get_cursor_position()); +pub struct ProcState { + pub widget_states: HashMap, + pub force_update: Option, + pub force_update_all: bool, +} - let removed_char = self +impl ProcState { + pub fn init(widget_states: HashMap) -> Self { + ProcState { + widget_states, + force_update: None, + force_update_all: false, + } + } +} + +pub struct NetWidgetState { + pub current_display_time: u64, + pub autohide_timer: Option, +} + +impl NetWidgetState { + pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { + NetWidgetState { + current_display_time, + autohide_timer, + } + } +} + +pub struct NetState { + pub force_update: Option, + pub widget_states: HashMap, +} + +impl NetState { + pub fn init(widget_states: HashMap) -> Self { + NetState { + force_update: None, + widget_states, + } + } +} + +pub struct CpuWidgetState { + pub current_display_time: u64, + pub is_legend_hidden: bool, + pub is_showing_tray: bool, + pub core_show_vec: Vec, + pub num_cpus_shown: usize, + pub autohide_timer: Option, + pub scroll_state: AppScrollWidgetState, +} + +impl CpuWidgetState { + pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { + CpuWidgetState { + current_display_time, + is_legend_hidden: false, + is_showing_tray: false, + core_show_vec: Vec::new(), + num_cpus_shown: 0, + autohide_timer, + scroll_state: AppScrollWidgetState::default(), + } + } +} + +pub struct CpuState { + pub force_update: Option, + pub widget_states: HashMap, + pub num_cpus_total: usize, +} + +impl CpuState { + pub fn init(widget_states: HashMap) -> Self { + CpuState { + force_update: None, + widget_states, + num_cpus_total: 0, + } + } +} + +pub struct MemWidgetState { + pub current_display_time: u64, + pub autohide_timer: Option, +} + +impl MemWidgetState { + pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { + MemWidgetState { + current_display_time, + autohide_timer, + } + } +} + +pub struct MemState { + pub force_update: Option, + pub widget_states: HashMap, +} + +impl MemState { + pub fn init(widget_states: HashMap) -> Self { + MemState { + force_update: None, + widget_states, + } + } +} + +pub struct TempWidgetState { + pub scroll_state: AppScrollWidgetState, +} + +impl TempWidgetState { + pub fn init() -> Self { + TempWidgetState { + scroll_state: AppScrollWidgetState::default(), + } + } +} + +pub struct TempState { + pub widget_states: HashMap, +} + +impl TempState { + pub fn init(widget_states: HashMap) -> Self { + TempState { widget_states } + } +} + +pub struct DiskWidgetState { + pub scroll_state: AppScrollWidgetState, +} + +impl DiskWidgetState { + pub fn init() -> Self { + DiskWidgetState { + scroll_state: AppScrollWidgetState::default(), + } + } +} + +pub struct DiskState { + pub widget_states: HashMap, +} + +impl DiskState { + pub fn init(widget_states: HashMap) -> Self { + DiskState { widget_states } + } +} + +pub struct BasicTableWidgetState { + // Since this is intended (currently) to only be used for ONE widget, that's + // how it's going to be written. If we want to allow for multiple of these, + // then we can expand outwards with a normal BasicTableState and a hashmap + pub currently_displayed_widget_type: BottomWidgetType, + pub currently_displayed_widget_id: u64, + pub widget_id: i64, +} + +#[derive(TypedBuilder)] +pub struct App { + #[builder(default = false, setter(skip))] + awaiting_second_char: bool, + + #[builder(default, setter(skip))] + second_char: Option, + + #[builder(default, setter(skip))] + pub dd_err: Option, + + #[builder(default, setter(skip))] + to_delete_process_list: Option<(String, Vec)>, + + #[builder(default = false, setter(skip))] + pub is_frozen: bool, + + #[builder(default = Instant::now(), setter(skip))] + last_key_press: Instant, + + #[builder(default, setter(skip))] + pub canvas_data: canvas::DisplayableData, + + #[builder(default, setter(skip))] + pub data_collection: DataCollection, + + #[builder(default, setter(skip))] + pub delete_dialog_state: AppDeleteDialogState, + + #[builder(default, setter(skip))] + pub help_dialog_state: AppHelpDialogState, + + #[builder(default = false, setter(skip))] + pub is_expanded: bool, + + #[builder(default = false, setter(skip))] + pub is_resized: bool, + + pub cpu_state: CpuState, + pub mem_state: MemState, + pub net_state: NetState, + pub proc_state: ProcState, + pub temp_state: TempState, + pub disk_state: DiskState, + + pub basic_table_widget_state: Option, + + pub app_config_fields: AppConfigFields, + pub widget_map: HashMap, + pub current_widget: BottomWidget, +} + +impl App { + pub fn reset(&mut self) { + // Reset multi + self.reset_multi_tap_keys(); + + // Reset dialog state + self.help_dialog_state.is_showing_help = false; + self.delete_dialog_state.is_showing_dd = false; + + // Close all searches and reset it + self.proc_state + .widget_states + .values_mut() + .for_each(|state| { + state.process_search_state.search_state.reset(); + }); + self.proc_state.force_update_all = true; + + // Reset all CPU filter states + self.cpu_state.widget_states.values_mut().for_each(|state| { + for show_vec_state in &mut state.core_show_vec { + *show_vec_state = true; + } + state.num_cpus_shown = state.core_show_vec.len(); + }); + + // Clear current delete list + self.to_delete_process_list = None; + self.dd_err = None; + + // Unfreeze. + self.is_frozen = false; + + // Reset zoom + self.reset_cpu_zoom(); + self.reset_mem_zoom(); + self.reset_net_zoom(); + + // Reset data + self.data_collection.reset(); + } + + pub fn on_esc(&mut self) { + self.reset_multi_tap_keys(); + if self.is_in_dialog() { + self.help_dialog_state.is_showing_help = false; + self.help_dialog_state.current_category = AppHelpCategory::General; + self.delete_dialog_state.is_showing_dd = false; + self.delete_dialog_state.is_on_yes = false; + self.to_delete_process_list = None; + self.dd_err = None; + } else if self.is_filtering_or_searching() { + match self.current_widget.widget_type { + BottomWidgetType::Cpu => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + cpu_widget_state.is_showing_tray = false; + if cpu_widget_state.scroll_state.current_scroll_position + >= cpu_widget_state.num_cpus_shown as u64 + { + let new_position = + max(0, cpu_widget_state.num_cpus_shown as i64 - 1) as u64; + cpu_widget_state.scroll_state.current_scroll_position = new_position; + cpu_widget_state.scroll_state.previous_scroll_position = 0; + } + } + } + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + cpu_widget_state.is_showing_tray = false; + if cpu_widget_state.scroll_state.current_scroll_position + >= cpu_widget_state.num_cpus_shown as u64 + { + let new_position = + max(0, cpu_widget_state.num_cpus_shown as i64 - 1) as u64; + cpu_widget_state.scroll_state.current_scroll_position = new_position; + cpu_widget_state.scroll_state.previous_scroll_position = 0; + } + } + } + BottomWidgetType::Proc => { + if let Some(current_proc_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + if current_proc_state.is_search_enabled() { + current_proc_state + .process_search_state + .search_state + .is_enabled = false; + } + } + } + BottomWidgetType::ProcSearch => { + if let Some(current_proc_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if current_proc_state.is_search_enabled() { + current_proc_state + .process_search_state + .search_state + .is_enabled = false; + self.move_widget_selection_up(); + } + } + } + _ => {} + } + } else if self.is_expanded { + self.is_expanded = false; + self.is_resized = true; + } + } + + pub fn is_in_search_widget(&self) -> bool { + matches!( + self.current_widget.widget_type, + BottomWidgetType::ProcSearch + ) + } + + fn is_filtering_or_searching(&self) -> bool { + match self.current_widget.widget_type { + BottomWidgetType::Cpu => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get(&self.current_widget.widget_id) + { + cpu_widget_state.is_showing_tray + } else { + false + } + } + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get(&(self.current_widget.widget_id - 1)) + { + cpu_widget_state.is_showing_tray + } else { + false + } + } + BottomWidgetType::Proc => { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get(&self.current_widget.widget_id) + { + proc_widget_state + .process_search_state + .search_state + .is_enabled + } else { + false + } + } + BottomWidgetType::ProcSearch => { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get(&(self.current_widget.widget_id - 1)) + { + proc_widget_state + .process_search_state + .search_state + .is_enabled + } else { + false + } + } + _ => false, + } + } + + fn reset_multi_tap_keys(&mut self) { + self.awaiting_second_char = false; + self.second_char = None; + } + + fn is_in_dialog(&self) -> bool { + self.help_dialog_state.is_showing_help || self.delete_dialog_state.is_showing_dd + } + + pub fn on_tab(&mut self) { + // Disallow usage whilst in a dialog and only in processes + + let is_in_search_widget = self.is_in_search_widget(); + if !self.is_in_dialog() { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget { + if !proc_widget_state.is_grouped { + if proc_widget_state.process_search_state.is_searching_with_pid { + self.search_with_name(); + } else { + self.search_with_pid(); + } + } + } else { + // Toggles process widget grouping state + proc_widget_state.is_grouped = !(proc_widget_state.is_grouped); + if proc_widget_state.is_grouped { + self.search_with_name(); + } + } + } + } + } + + /// I don't like this, but removing it causes a bunch of breakage. + /// Use ``proc_widget_state.is_grouped`` if possible! + pub fn is_grouped(&self, widget_id: u64) -> bool { + if let Some(proc_widget_state) = self.proc_state.widget_states.get(&widget_id) { + proc_widget_state.is_grouped + } else { + false + } + } + + /// "On space" if we don't want to treat is as a character. + pub fn on_space(&mut self) { + if let BottomWidgetType::CpuLegend = self.current_widget.widget_type { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + let curr_posn = cpu_widget_state.scroll_state.current_scroll_position; + if cpu_widget_state.is_showing_tray + && curr_posn < self.data_collection.cpu_harvest.len() as u64 + { + cpu_widget_state.core_show_vec[curr_posn as usize] = + !cpu_widget_state.core_show_vec[curr_posn as usize]; + + if !self.app_config_fields.show_disabled_data { + if !cpu_widget_state.core_show_vec[curr_posn as usize] { + cpu_widget_state.num_cpus_shown -= 1; + } else { + cpu_widget_state.num_cpus_shown += 1; + } + } + } + } + } + } + + pub fn on_slash(&mut self) { + if !self.is_in_dialog() { + match self.current_widget.widget_type { + BottomWidgetType::Proc => { + // Toggle on + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + proc_widget_state + .process_search_state + .search_state + .is_enabled = true; + if proc_widget_state.is_grouped { + self.search_with_name(); + } + self.move_widget_selection_down(); + } + } + BottomWidgetType::Cpu => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + cpu_widget_state.is_showing_tray = true; + if self.app_config_fields.left_legend { + self.move_widget_selection_left(); + } else { + self.move_widget_selection_right(); + } + } + } + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + cpu_widget_state.is_showing_tray = true; + if self.app_config_fields.left_legend { + self.move_widget_selection_left(); + } else { + self.move_widget_selection_right(); + } + } + } + _ => {} + } + } + } + + pub fn search_with_pid(&mut self) { + if !self.is_in_dialog() { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if proc_widget_state .process_search_state .search_state - .current_search_query - .remove(self.get_cursor_position()); + .is_enabled + { + proc_widget_state.process_search_state.is_searching_with_pid = true; + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } + } + } - self.process_search_state.search_state.grapheme_cursor = GraphemeCursor::new( - self.get_cursor_position(), - self.process_search_state + pub fn search_with_name(&mut self) { + if !self.is_in_dialog() { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if proc_widget_state + .process_search_state + .search_state + .is_enabled + { + proc_widget_state.process_search_state.is_searching_with_pid = false; + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } + } + } + + pub fn toggle_ignore_case(&mut self) { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget && proc_widget_state.is_search_enabled() { + proc_widget_state + .process_search_state + .search_toggle_ignore_case(); + proc_widget_state.update_regex(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } + } + + pub fn toggle_search_whole_word(&mut self) { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget && proc_widget_state.is_search_enabled() { + proc_widget_state + .process_search_state + .search_toggle_whole_word(); + proc_widget_state.update_regex(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } + } + + pub fn toggle_search_regex(&mut self) { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget && proc_widget_state.is_search_enabled() { + proc_widget_state.process_search_state.search_toggle_regex(); + proc_widget_state.update_regex(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } + } + + /// One of two functions allowed to run while in a dialog... + pub fn on_enter(&mut self) { + if self.delete_dialog_state.is_showing_dd { + if self.delete_dialog_state.is_on_yes { + // If within dd... + if self.dd_err.is_none() { + // Also ensure that we didn't just fail a dd... + let dd_result = self.kill_highlighted_process(); + self.delete_dialog_state.is_on_yes = false; + + // Check if there was an issue... if so, inform the user. + if let Err(dd_err) = dd_result { + self.dd_err = Some(dd_err.to_string()); + } else { + self.delete_dialog_state.is_showing_dd = false; + } + } + } else { + self.delete_dialog_state.is_showing_dd = false; + } + } else if !self.is_in_dialog() && !self.app_config_fields.use_basic_mode { + // Pop-out mode. We ignore if in process search. + + match self.current_widget.widget_type { + BottomWidgetType::ProcSearch => {} + _ => { + self.is_expanded = true; + self.is_resized = true; + } + } + } + } + + pub fn on_delete(&mut self) { + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget { + if proc_widget_state + .process_search_state + .search_state + .is_enabled + && proc_widget_state.get_cursor_position() + < proc_widget_state + .process_search_state + .search_state + .current_search_query + .len() + { + proc_widget_state + .process_search_state + .search_state + .current_search_query + .remove(proc_widget_state.get_cursor_position()); + + proc_widget_state + .process_search_state + .search_state + .grapheme_cursor = GraphemeCursor::new( + proc_widget_state.get_cursor_position(), + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + true, + ); + + proc_widget_state.update_regex(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } else { + self.start_dd() + } + } + } + } + + pub fn on_backspace(&mut self) { + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget + && proc_widget_state + .process_search_state + .search_state + .is_enabled + && proc_widget_state.get_cursor_position() > 0 + { + proc_widget_state.search_walk_back(proc_widget_state.get_cursor_position()); + + let removed_char = proc_widget_state + .process_search_state .search_state .current_search_query - .len(), - true, - ); + .remove(proc_widget_state.get_cursor_position()); - self.process_search_state.search_state.char_cursor_position -= - UnicodeWidthChar::width(removed_char).unwrap_or(0); - self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT; + proc_widget_state + .process_search_state + .search_state + .grapheme_cursor = GraphemeCursor::new( + proc_widget_state.get_cursor_position(), + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + true, + ); - self.update_regex(); - self.force_update_processes = true; + proc_widget_state + .process_search_state + .search_state + .char_cursor_position -= UnicodeWidthChar::width(removed_char).unwrap_or(0); + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::LEFT; + + proc_widget_state.update_regex(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } } } } pub fn get_current_regex_matcher( - &self, + &self, widget_id: u64, ) -> &Option> { - &self.process_search_state.search_state.current_regex + match self.proc_state.widget_states.get(&widget_id) { + Some(proc_widget_state) => { + &proc_widget_state + .process_search_state + .search_state + .current_regex + } + None => &None, + } } pub fn on_up_key(&mut self) { if !self.is_in_dialog() { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - } else { - self.decrement_position_count(); - } + self.decrement_position_count(); } } pub fn on_down_key(&mut self) { if !self.is_in_dialog() { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - } else { - self.increment_position_count(); - } + self.increment_position_count(); } } pub fn on_left_key(&mut self) { if !self.is_in_dialog() { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - let prev_cursor = self.get_cursor_position(); - self.search_walk_back(self.get_cursor_position()); - if self.get_cursor_position() < prev_cursor { - let str_slice = &self.process_search_state.search_state.current_search_query - [self.get_cursor_position()..prev_cursor]; - self.process_search_state.search_state.char_cursor_position -= - UnicodeWidthStr::width(str_slice); - self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT; + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget { + let prev_cursor = proc_widget_state.get_cursor_position(); + proc_widget_state.search_walk_back(proc_widget_state.get_cursor_position()); + if proc_widget_state.get_cursor_position() < prev_cursor { + let str_slice = &proc_widget_state + .process_search_state + .search_state + .current_search_query + [proc_widget_state.get_cursor_position()..prev_cursor]; + proc_widget_state + .process_search_state + .search_state + .char_cursor_position -= UnicodeWidthStr::width(str_slice); + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::LEFT; + } + } } } } else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes { @@ -866,16 +1113,33 @@ impl App { pub fn on_right_key(&mut self) { if !self.is_in_dialog() { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - let prev_cursor = self.get_cursor_position(); - self.search_walk_forward(self.get_cursor_position()); - if self.get_cursor_position() > prev_cursor { - let str_slice = &self.process_search_state.search_state.current_search_query - [prev_cursor..self.get_cursor_position()]; - self.process_search_state.search_state.char_cursor_position += - UnicodeWidthStr::width(str_slice); - self.process_search_state.search_state.cursor_direction = - CursorDirection::RIGHT; + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget { + let prev_cursor = proc_widget_state.get_cursor_position(); + proc_widget_state + .search_walk_forward(proc_widget_state.get_cursor_position()); + if proc_widget_state.get_cursor_position() > prev_cursor { + let str_slice = &proc_widget_state + .process_search_state + .search_state + .current_search_query + [prev_cursor..proc_widget_state.get_cursor_position()]; + proc_widget_state + .process_search_state + .search_state + .char_cursor_position += UnicodeWidthStr::width(str_slice); + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::RIGHT; + } + } } } } else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes { @@ -885,86 +1149,141 @@ impl App { pub fn skip_cursor_beginning(&mut self) { if !self.is_in_dialog() { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - self.process_search_state.search_state.grapheme_cursor = GraphemeCursor::new( - 0, - self.process_search_state - .search_state - .current_search_query - .len(), - true, - ); - self.process_search_state.search_state.char_cursor_position = 0; - self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT; + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget { + proc_widget_state + .process_search_state + .search_state + .grapheme_cursor = GraphemeCursor::new( + 0, + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + true, + ); + proc_widget_state + .process_search_state + .search_state + .char_cursor_position = 0; + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::LEFT; + } + } } } } pub fn skip_cursor_end(&mut self) { if !self.is_in_dialog() { - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - self.process_search_state.search_state.grapheme_cursor = GraphemeCursor::new( - self.process_search_state - .search_state - .current_search_query - .len(), - self.process_search_state - .search_state - .current_search_query - .len(), - true, - ); - self.process_search_state.search_state.char_cursor_position = - UnicodeWidthStr::width( - self.process_search_state + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + if is_in_search_widget { + proc_widget_state + .process_search_state .search_state - .current_search_query - .as_str(), - ); - self.process_search_state.search_state.cursor_direction = CursorDirection::RIGHT; + .grapheme_cursor = GraphemeCursor::new( + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + true, + ); + proc_widget_state + .process_search_state + .search_state + .char_cursor_position = UnicodeWidthStr::width( + proc_widget_state + .process_search_state + .search_state + .current_search_query + .as_str(), + ); + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::RIGHT; + } + } + } + } + } + + pub fn clear_search(&mut self) { + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + proc_widget_state.clear_search(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); } } } pub fn start_dd(&mut self) { - if self - .app_scroll_positions - .process_scroll_state - .current_scroll_position - < self.canvas_data.finalized_process_data.len() as u64 + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get(&self.current_widget.widget_id) { - let current_process = if self.is_grouped() { - let group_pids = &self.canvas_data.finalized_process_data[self - .app_scroll_positions - .process_scroll_state - .current_scroll_position - as usize] - .group_pids; + if let Some(corresponding_filtered_process_list) = self + .canvas_data + .finalized_process_data_map + .get(&self.current_widget.widget_id) + { + if proc_widget_state.scroll_state.current_scroll_position + < self.canvas_data.finalized_process_data_map.len() as u64 + { + let current_process = if self.is_grouped(self.current_widget.widget_id) { + let group_pids = &corresponding_filtered_process_list + [proc_widget_state.scroll_state.current_scroll_position as usize] + .group_pids; - let mut ret = ("".to_string(), group_pids.clone()); + let mut ret = ("".to_string(), group_pids.clone()); - for pid in group_pids { - if let Some(process) = self.canvas_data.process_data.get(&pid) { - ret.0 = process.name.clone(); - break; - } + for pid in group_pids { + if let Some(process) = self.canvas_data.process_data.get(&pid) { + ret.0 = process.name.clone(); + break; + } + } + ret + } else { + let process = corresponding_filtered_process_list + [proc_widget_state.scroll_state.current_scroll_position as usize] + .clone(); + (process.name.clone(), vec![process.pid]) + }; + + self.to_delete_process_list = Some(current_process); + self.delete_dialog_state.is_showing_dd = true; } - ret - } else { - let process = self.canvas_data.finalized_process_data[self - .app_scroll_positions - .process_scroll_state - .current_scroll_position - as usize] - .clone(); - (process.name.clone(), vec![process.pid]) - }; - self.to_delete_process_list = Some(current_process); - self.delete_dialog_state.is_showing_dd = true; + self.reset_multi_tap_keys(); + } } - - self.reset_multi_tap_keys(); } pub fn on_char_key(&mut self, caught_char: char) { @@ -985,169 +1304,62 @@ impl App { } self.last_key_press = current_key_press_inst; - if let WidgetPosition::ProcessSearch = self.current_widget_selected { - if UnicodeWidthStr::width( - self.process_search_state - .search_state - .current_search_query - .as_str(), - ) <= MAX_SEARCH_LENGTH + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + let is_in_search_widget = self.is_in_search_widget(); + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) { - self.process_search_state - .search_state - .current_search_query - .insert(self.get_cursor_position(), caught_char); - - self.process_search_state.search_state.grapheme_cursor = GraphemeCursor::new( - self.get_cursor_position(), - self.process_search_state + if is_in_search_widget + && proc_widget_state.is_search_enabled() + && UnicodeWidthStr::width( + proc_widget_state + .process_search_state + .search_state + .current_search_query + .as_str(), + ) <= MAX_SEARCH_LENGTH + { + proc_widget_state + .process_search_state .search_state .current_search_query - .len(), - true, - ); - self.search_walk_forward(self.get_cursor_position()); + .insert(proc_widget_state.get_cursor_position(), caught_char); - self.process_search_state.search_state.char_cursor_position += - UnicodeWidthChar::width(caught_char).unwrap_or(0); + proc_widget_state + .process_search_state + .search_state + .grapheme_cursor = GraphemeCursor::new( + proc_widget_state.get_cursor_position(), + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + true, + ); + proc_widget_state + .search_walk_forward(proc_widget_state.get_cursor_position()); - self.update_regex(); - self.force_update_processes = true; - self.process_search_state.search_state.cursor_direction = - CursorDirection::RIGHT; - } - } else { - match caught_char { - '/' => { - self.on_slash(); - } - 'd' => { - if let WidgetPosition::Process = self.current_widget_selected { - let mut is_first_d = true; - if let Some(second_char) = self.second_char { - if self.awaiting_second_char && second_char == 'd' { - is_first_d = false; - self.awaiting_second_char = false; - self.second_char = None; + proc_widget_state + .process_search_state + .search_state + .char_cursor_position += + UnicodeWidthChar::width(caught_char).unwrap_or(0); - self.start_dd(); - } - } + proc_widget_state.update_regex(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::RIGHT; - if is_first_d { - self.awaiting_second_char = true; - self.second_char = Some('d'); - } - } - } - 'g' => { - let mut is_first_g = true; - if let Some(second_char) = self.second_char { - if self.awaiting_second_char && second_char == 'g' { - is_first_g = false; - self.awaiting_second_char = false; - self.second_char = None; - self.skip_to_first(); - } - } - - if is_first_g { - self.awaiting_second_char = true; - self.second_char = Some('g'); - } - } - 'G' => self.skip_to_last(), - 'k' => self.decrement_position_count(), - 'j' => self.increment_position_count(), - 'f' => { - self.is_frozen = !self.is_frozen; - if self.is_frozen { - self.data_collection.set_frozen_time(); - } - } - 'c' => { - match self.process_sorting_type { - processes::ProcessSorting::CPU => { - self.process_sorting_reverse = !self.process_sorting_reverse - } - _ => { - self.process_sorting_type = processes::ProcessSorting::CPU; - self.process_sorting_reverse = true; - } - } - self.force_update_processes = true; - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = 0; - } - 'm' => { - match self.process_sorting_type { - processes::ProcessSorting::MEM => { - self.process_sorting_reverse = !self.process_sorting_reverse - } - _ => { - self.process_sorting_type = processes::ProcessSorting::MEM; - self.process_sorting_reverse = true; - } - } - self.force_update_processes = true; - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = 0; - } - 'p' => { - // Disable if grouping - if !self.enable_grouping { - match self.process_sorting_type { - processes::ProcessSorting::PID => { - self.process_sorting_reverse = !self.process_sorting_reverse - } - _ => { - self.process_sorting_type = processes::ProcessSorting::PID; - self.process_sorting_reverse = false; - } - } - self.force_update_processes = true; - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = 0; - } - } - 'n' => { - match self.process_sorting_type { - processes::ProcessSorting::NAME => { - self.process_sorting_reverse = !self.process_sorting_reverse - } - _ => { - self.process_sorting_type = processes::ProcessSorting::NAME; - self.process_sorting_reverse = false; - } - } - self.force_update_processes = true; - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = 0; - } - '?' => { - self.help_dialog_state.is_showing_help = true; - } - 'H' => self.move_widget_selection_left(), - 'L' => self.move_widget_selection_right(), - 'K' => self.move_widget_selection_up(), - 'J' => self.move_widget_selection_down(), - ' ' => self.on_space(), - '+' => self.zoom_in(), - '-' => self.zoom_out(), - '=' => self.reset_zoom(), - _ => {} - } - - if let Some(second_char) = self.second_char { - if self.awaiting_second_char && caught_char != second_char { - self.awaiting_second_char = false; + return; } } } + self.handle_char(caught_char); } else if self.help_dialog_state.is_showing_help { match caught_char { '1' => self.help_dialog_state.current_category = AppHelpCategory::General, @@ -1158,68 +1370,283 @@ impl App { } } + fn handle_char(&mut self, caught_char: char) { + match caught_char { + '/' => { + self.on_slash(); + } + 'd' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + let mut is_first_d = true; + if let Some(second_char) = self.second_char { + if self.awaiting_second_char && second_char == 'd' { + is_first_d = false; + self.awaiting_second_char = false; + self.second_char = None; + + self.start_dd(); + } + } + + if is_first_d { + self.awaiting_second_char = true; + self.second_char = Some('d'); + } + } + } + 'g' => { + let mut is_first_g = true; + if let Some(second_char) = self.second_char { + if self.awaiting_second_char && second_char == 'g' { + is_first_g = false; + self.awaiting_second_char = false; + self.second_char = None; + self.skip_to_first(); + } + } + + if is_first_g { + self.awaiting_second_char = true; + self.second_char = Some('g'); + } + } + 'G' => self.skip_to_last(), + 'k' => self.decrement_position_count(), + 'j' => self.increment_position_count(), + 'f' => { + self.is_frozen = !self.is_frozen; + if self.is_frozen { + self.data_collection.set_frozen_time(); + } + } + 'c' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + match proc_widget_state.process_sorting_type { + processes::ProcessSorting::CPU => { + proc_widget_state.process_sorting_reverse = + !proc_widget_state.process_sorting_reverse + } + _ => { + proc_widget_state.process_sorting_type = + processes::ProcessSorting::CPU; + proc_widget_state.process_sorting_reverse = true; + } + } + self.proc_state.force_update = Some(self.current_widget.widget_id); + + self.skip_to_first(); + } + } + } + 'm' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + match proc_widget_state.process_sorting_type { + processes::ProcessSorting::MEM => { + proc_widget_state.process_sorting_reverse = + !proc_widget_state.process_sorting_reverse + } + _ => { + proc_widget_state.process_sorting_type = + processes::ProcessSorting::MEM; + proc_widget_state.process_sorting_reverse = true; + } + } + self.proc_state.force_update = Some(self.current_widget.widget_id); + self.skip_to_first(); + } + } + } + 'p' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + // Skip if grouped + if !proc_widget_state.is_grouped { + match proc_widget_state.process_sorting_type { + processes::ProcessSorting::PID => { + proc_widget_state.process_sorting_reverse = + !proc_widget_state.process_sorting_reverse + } + _ => { + proc_widget_state.process_sorting_type = + processes::ProcessSorting::PID; + proc_widget_state.process_sorting_reverse = false; + } + } + self.proc_state.force_update = Some(self.current_widget.widget_id); + self.skip_to_first(); + } + } + } + } + 'n' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + match proc_widget_state.process_sorting_type { + processes::ProcessSorting::NAME => { + proc_widget_state.process_sorting_reverse = + !proc_widget_state.process_sorting_reverse + } + _ => { + proc_widget_state.process_sorting_type = + processes::ProcessSorting::NAME; + proc_widget_state.process_sorting_reverse = false; + } + } + self.proc_state.force_update = Some(self.current_widget.widget_id); + self.skip_to_first(); + } + } + } + '?' => { + self.help_dialog_state.is_showing_help = true; + } + 'H' => self.move_widget_selection_left(), + 'L' => self.move_widget_selection_right(), + 'K' => self.move_widget_selection_up(), + 'J' => self.move_widget_selection_down(), + ' ' => self.on_space(), + '+' => self.zoom_in(), + '-' => self.zoom_out(), + '=' => self.reset_zoom(), + _ => {} + } + + if let Some(second_char) = self.second_char { + if self.awaiting_second_char && caught_char != second_char { + self.awaiting_second_char = false; + } + } + } + pub fn kill_highlighted_process(&mut self) -> Result<()> { - // Technically unnecessary but this is a good check... - if let WidgetPosition::Process = self.current_widget_selected { + if let BottomWidgetType::Proc = self.current_widget.widget_type { if let Some(current_selected_processes) = &self.to_delete_process_list { for pid in ¤t_selected_processes.1 { process_killer::kill_process_given_pid(*pid)?; } } self.to_delete_process_list = None; + Ok(()) + } else { + Err(BottomError::GenericError( + "Cannot kill processes if the current widget is not the Process widget!" + .to_string(), + )) } - Ok(()) } pub fn get_to_delete_processes(&self) -> Option<(String, Vec)> { self.to_delete_process_list.clone() } - // TODO: [MODULARITY] Do NOT hard code this in thu future! - // - // General idea for now: - // CPU -(down)> MEM - // MEM -(down)> Network, -(right)> TEMP - // TEMP -(down)> Disk, -(left)> MEM, -(up)> CPU - // Disk -(down)> Processes, -(left)> MEM, -(up)> TEMP - // Network -(up)> MEM, -(right)> PROC - // PROC -(up)> Disk, -(down)> PROC_SEARCH, -(left)> Network - // PROC_SEARCH -(up)> PROC, -(left)> Network pub fn move_widget_selection_left(&mut self) { if !self.is_in_dialog() && !self.is_expanded { - if self.app_config_fields.use_basic_mode { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::BasicNet => WidgetPosition::BasicMem, - WidgetPosition::Process => WidgetPosition::Disk, - WidgetPosition::ProcessSearch => WidgetPosition::Disk, - WidgetPosition::Disk => WidgetPosition::Temp, - WidgetPosition::Temp => WidgetPosition::Process, - _ => self.current_widget_selected, - }; - } else { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu if self.app_config_fields.left_legend => { - WidgetPosition::CpuLegend + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.left_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + match new_widget.widget_type { + BottomWidgetType::Temp + | BottomWidgetType::Proc + | BottomWidgetType::ProcSearch + | BottomWidgetType::Disk + if self.basic_table_widget_state.is_some() => + { + if let Some(basic_table_widget_state) = + &mut self.basic_table_widget_state + { + basic_table_widget_state.currently_displayed_widget_id = + new_widget_id; + basic_table_widget_state.currently_displayed_widget_type = + new_widget.widget_type.clone(); + } + self.current_widget = new_widget.clone(); + } + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = + self.cpu_state.widget_states.get(&(new_widget_id - 1)) + { + if cpu_widget_state.is_legend_hidden { + if let Some(next_new_widget_id) = new_widget.left_neighbour + { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } else { + self.current_widget = new_widget.clone(); + } + } + } + BottomWidgetType::ProcSearch => { + if let Some(proc_widget_state) = + self.proc_state.widget_states.get(&(new_widget_id - 1)) + { + if proc_widget_state.is_search_enabled() { + self.current_widget = new_widget.clone(); + } else if let Some(next_new_widget_id) = new_widget.up_neighbour + { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } + } + _ => self.current_widget = new_widget.clone(), + } } - WidgetPosition::CpuLegend if !self.app_config_fields.left_legend => { - WidgetPosition::Cpu - } - WidgetPosition::Process => WidgetPosition::Network, - WidgetPosition::ProcessSearch => WidgetPosition::Network, - WidgetPosition::Disk => WidgetPosition::Mem, - WidgetPosition::Temp => WidgetPosition::Mem, - _ => self.current_widget_selected, - }; + } } } else if self.is_expanded { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu if self.app_config_fields.left_legend => { - WidgetPosition::CpuLegend + if self.app_config_fields.left_legend { + if let BottomWidgetType::Cpu = self.current_widget.widget_type { + if let Some(current_widget) = + self.widget_map.get(&self.current_widget.widget_id) + { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get(&self.current_widget.widget_id) + { + if !cpu_widget_state.is_legend_hidden { + if let Some(new_widget_id) = current_widget.left_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + self.current_widget = new_widget.clone(); + } + } + } + } + } } - WidgetPosition::CpuLegend if !self.app_config_fields.left_legend => { - WidgetPosition::Cpu + } else if let BottomWidgetType::CpuLegend = self.current_widget.widget_type { + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.left_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + self.current_widget = new_widget.clone(); + } + } } - _ => self.current_widget_selected, } } @@ -1228,37 +1655,97 @@ impl App { pub fn move_widget_selection_right(&mut self) { if !self.is_in_dialog() && !self.is_expanded { - if self.app_config_fields.use_basic_mode { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::BasicMem => WidgetPosition::BasicNet, - WidgetPosition::Process => WidgetPosition::Temp, - WidgetPosition::ProcessSearch => WidgetPosition::Temp, - WidgetPosition::Disk => WidgetPosition::Process, - WidgetPosition::Temp => WidgetPosition::Disk, - _ => self.current_widget_selected, - }; - } else { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu if !self.app_config_fields.left_legend => { - WidgetPosition::CpuLegend + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.right_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + match new_widget.widget_type { + BottomWidgetType::Temp + | BottomWidgetType::Proc + | BottomWidgetType::ProcSearch + | BottomWidgetType::Disk + if self.basic_table_widget_state.is_some() => + { + if let Some(basic_table_widget_state) = + &mut self.basic_table_widget_state + { + basic_table_widget_state.currently_displayed_widget_id = + new_widget_id; + basic_table_widget_state.currently_displayed_widget_type = + new_widget.widget_type.clone(); + } + self.current_widget = new_widget.clone(); + } + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = + self.cpu_state.widget_states.get(&(new_widget_id - 1)) + { + if cpu_widget_state.is_legend_hidden { + if let Some(next_new_widget_id) = new_widget.right_neighbour + { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } else { + self.current_widget = new_widget.clone(); + } + } + } + BottomWidgetType::ProcSearch => { + if let Some(proc_widget_state) = + self.proc_state.widget_states.get(&(new_widget_id - 1)) + { + if proc_widget_state.is_search_enabled() { + self.current_widget = new_widget.clone(); + } else if let Some(next_new_widget_id) = new_widget.up_neighbour + { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } + } + + _ => { + self.current_widget = new_widget.clone(); + } + } } - WidgetPosition::CpuLegend if self.app_config_fields.left_legend => { - WidgetPosition::Cpu - } - WidgetPosition::Mem => WidgetPosition::Temp, - WidgetPosition::Network => WidgetPosition::Process, - _ => self.current_widget_selected, - }; + } } } else if self.is_expanded { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu if !self.app_config_fields.left_legend => { - WidgetPosition::CpuLegend + if self.app_config_fields.left_legend { + if let BottomWidgetType::CpuLegend = self.current_widget.widget_type { + if let Some(current_widget) = + self.widget_map.get(&self.current_widget.widget_id) + { + if let Some(new_widget_id) = current_widget.right_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + self.current_widget = new_widget.clone(); + } + } + } } - WidgetPosition::CpuLegend if self.app_config_fields.left_legend => { - WidgetPosition::Cpu + } else if let BottomWidgetType::Cpu = self.current_widget.widget_type { + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get(&self.current_widget.widget_id) + { + if !cpu_widget_state.is_legend_hidden { + if let Some(new_widget_id) = current_widget.right_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + self.current_widget = new_widget.clone(); + } + } + } + } } - _ => self.current_widget_selected, } } @@ -1267,35 +1754,67 @@ impl App { pub fn move_widget_selection_up(&mut self) { if !self.is_in_dialog() && !self.is_expanded { - if self.app_config_fields.use_basic_mode { - if self.current_widget_selected.is_widget_table() { - self.previous_basic_table_selected = self.current_widget_selected; + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.up_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + match new_widget.widget_type { + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = + self.cpu_state.widget_states.get(&(new_widget_id - 1)) + { + if cpu_widget_state.is_legend_hidden { + if let Some(next_new_widget) = + self.widget_map.get(&(new_widget_id - 1)) + { + self.current_widget = next_new_widget.clone(); + } + } else { + self.current_widget = new_widget.clone(); + } + } + } + BottomWidgetType::ProcSearch => { + if let Some(proc_widget_state) = + self.proc_state.widget_states.get(&(new_widget_id - 1)) + { + if proc_widget_state.is_search_enabled() { + self.current_widget = new_widget.clone(); + } else if let Some(next_new_widget_id) = new_widget.up_neighbour + { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } + } + BottomWidgetType::BasicTables => { + if let Some(next_new_widget_id) = new_widget.up_neighbour { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } + _ => { + self.current_widget = new_widget.clone(); + } + } + } } - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::BasicMem => WidgetPosition::BasicCpu, - WidgetPosition::BasicNet => WidgetPosition::BasicCpu, - WidgetPosition::Process => WidgetPosition::BasicMem, - WidgetPosition::ProcessSearch => WidgetPosition::Process, - WidgetPosition::Temp => WidgetPosition::BasicMem, - WidgetPosition::Disk => WidgetPosition::BasicMem, - _ => self.current_widget_selected, - }; - } else { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Mem => WidgetPosition::Cpu, - WidgetPosition::Network => WidgetPosition::Mem, - WidgetPosition::Process => WidgetPosition::Disk, - WidgetPosition::ProcessSearch => WidgetPosition::Process, - WidgetPosition::Temp => WidgetPosition::Cpu, - WidgetPosition::Disk => WidgetPosition::Temp, - _ => self.current_widget_selected, - }; } } else if self.is_expanded { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::ProcessSearch => WidgetPosition::Process, - _ => self.current_widget_selected, - }; + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.up_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + self.current_widget = new_widget.clone(); + } + } + } + } } self.reset_multi_tap_keys(); @@ -1303,47 +1822,80 @@ impl App { pub fn move_widget_selection_down(&mut self) { if !self.is_in_dialog() && !self.is_expanded { - if self.app_config_fields.use_basic_mode { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::BasicMem => self.previous_basic_table_selected, - WidgetPosition::BasicNet => self.previous_basic_table_selected, - WidgetPosition::BasicCpu => WidgetPosition::BasicMem, - WidgetPosition::Process => { - if self.is_searching() { - WidgetPosition::ProcessSearch - } else { - WidgetPosition::Process + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.down_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + match new_widget.widget_type { + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = + self.cpu_state.widget_states.get(&(new_widget_id - 1)) + { + if cpu_widget_state.is_legend_hidden { + if let Some(next_new_widget) = + self.widget_map.get(&(new_widget_id - 1)) + { + self.current_widget = next_new_widget.clone(); + } + } else { + self.current_widget = new_widget.clone(); + } + } + } + BottomWidgetType::ProcSearch => { + if let Some(proc_widget_state) = + self.proc_state.widget_states.get(&(new_widget_id - 1)) + { + if proc_widget_state.is_search_enabled() { + self.current_widget = new_widget.clone(); + } else if let Some(next_new_widget_id) = + new_widget.down_neighbour + { + if let Some(next_new_widget) = + self.widget_map.get(&next_new_widget_id) + { + self.current_widget = next_new_widget.clone(); + } + } + } + } + BottomWidgetType::BasicTables => { + // This means we're in basic mode. As such, then + // we want to move DOWN to the currently shown widget + if let Some(basic_table_widget_state) = + &self.basic_table_widget_state + { + if let Some(next_new_widget) = self.widget_map.get( + &basic_table_widget_state.currently_displayed_widget_id, + ) { + self.current_widget = next_new_widget.clone(); + } + } + } + _ => { + self.current_widget = new_widget.clone(); + } } } - _ => self.current_widget_selected, - }; - } else { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::CpuLegend => WidgetPosition::Mem, - WidgetPosition::Mem => WidgetPosition::Network, - WidgetPosition::Temp => WidgetPosition::Disk, - WidgetPosition::Disk => WidgetPosition::Process, - WidgetPosition::Process => { - if self.is_searching() { - WidgetPosition::ProcessSearch - } else { - WidgetPosition::Process - } - } - _ => self.current_widget_selected, - }; - } - } else if self.is_expanded { - self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Process => { - if self.is_searching() { - WidgetPosition::ProcessSearch - } else { - WidgetPosition::Process - } } - _ => self.current_widget_selected, - }; + } + } else if self.is_expanded { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(current_widget) = self.widget_map.get(&self.current_widget.widget_id) { + if let Some(new_widget_id) = current_widget.down_neighbour { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get(&self.current_widget.widget_id) + { + if proc_widget_state.is_search_enabled() { + self.current_widget = new_widget.clone(); + } + } + } + } + } + } } self.reset_multi_tap_keys(); @@ -1351,229 +1903,350 @@ impl App { pub fn skip_to_first(&mut self) { if !self.is_in_dialog() { - match self.current_widget_selected { - WidgetPosition::Process => { - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = 0 + match self.current_widget.widget_type { + BottomWidgetType::Proc => { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + proc_widget_state.scroll_state.current_scroll_position = 0; + proc_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } } - WidgetPosition::Temp => { - self.app_scroll_positions - .temp_scroll_state - .current_scroll_position = 0 + BottomWidgetType::Temp => { + if let Some(temp_widget_state) = self + .temp_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + temp_widget_state.scroll_state.current_scroll_position = 0; + temp_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } } - WidgetPosition::Disk => { - self.app_scroll_positions - .disk_scroll_state - .current_scroll_position = 0 + BottomWidgetType::Disk => { + if let Some(disk_widget_state) = self + .disk_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + disk_widget_state.scroll_state.current_scroll_position = 0; + disk_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } } - WidgetPosition::CpuLegend => { - self.app_scroll_positions - .cpu_scroll_state - .current_scroll_position = 0 + BottomWidgetType::CpuLegend => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + cpu_widget_state.scroll_state.current_scroll_position = 0; + cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } } _ => {} } - self.app_scroll_positions.scroll_direction = ScrollDirection::UP; self.reset_multi_tap_keys(); } } pub fn skip_to_last(&mut self) { if !self.is_in_dialog() { - match self.current_widget_selected { - WidgetPosition::Process => { - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = - self.canvas_data.finalized_process_data.len() as u64 - 1 + match self.current_widget.widget_type { + BottomWidgetType::Proc => { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + if let Some(finalized_process_data) = self + .canvas_data + .finalized_process_data_map + .get(&self.current_widget.widget_id) + { + if !self.canvas_data.finalized_process_data_map.is_empty() { + proc_widget_state.scroll_state.current_scroll_position = + finalized_process_data.len() as u64 - 1; + proc_widget_state.scroll_state.scroll_direction = + ScrollDirection::DOWN; + } + } + } } - WidgetPosition::Temp => { - self.app_scroll_positions - .temp_scroll_state - .current_scroll_position = - self.canvas_data.temp_sensor_data.len() as u64 - 1 + BottomWidgetType::Temp => { + if let Some(temp_widget_state) = self + .temp_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + if !self.canvas_data.temp_sensor_data.is_empty() { + temp_widget_state.scroll_state.current_scroll_position = + self.canvas_data.temp_sensor_data.len() as u64 - 1; + temp_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } + } } - WidgetPosition::Disk => { - self.app_scroll_positions - .disk_scroll_state - .current_scroll_position = self.canvas_data.disk_data.len() as u64 - 1 + BottomWidgetType::Disk => { + if let Some(disk_widget_state) = self + .disk_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + if !self.canvas_data.disk_data.is_empty() { + disk_widget_state.scroll_state.current_scroll_position = + self.canvas_data.disk_data.len() as u64 - 1; + disk_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } + } } - WidgetPosition::CpuLegend => { - self.app_scroll_positions - .cpu_scroll_state - .current_scroll_position = self.canvas_data.cpu_data.len() as u64 - 1; + BottomWidgetType::CpuLegend => { + let is_filtering_or_searching = self.is_filtering_or_searching(); + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let cap = if is_filtering_or_searching { + self.canvas_data.cpu_data.len() + } else { + cpu_widget_state.num_cpus_shown + } as u64; + + if cap > 0 { + cpu_widget_state.scroll_state.current_scroll_position = cap - 1; + cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } + } } _ => {} } - self.app_scroll_positions.scroll_direction = ScrollDirection::DOWN; self.reset_multi_tap_keys(); } } pub fn decrement_position_count(&mut self) { if !self.is_in_dialog() { - match self.current_widget_selected { - WidgetPosition::Process => self.change_process_position(-1), - WidgetPosition::Temp => self.change_temp_position(-1), - WidgetPosition::Disk => self.change_disk_position(-1), - WidgetPosition::CpuLegend => self.change_cpu_table_position(-1), + match self.current_widget.widget_type { + BottomWidgetType::Proc => self.change_process_position(-1), + BottomWidgetType::Temp => self.change_temp_position(-1), + BottomWidgetType::Disk => self.change_disk_position(-1), + BottomWidgetType::CpuLegend => self.change_cpu_table_position(-1), _ => {} } - self.app_scroll_positions.scroll_direction = ScrollDirection::UP; self.reset_multi_tap_keys(); } } pub fn increment_position_count(&mut self) { if !self.is_in_dialog() { - match self.current_widget_selected { - WidgetPosition::Process => self.change_process_position(1), - WidgetPosition::Temp => self.change_temp_position(1), - WidgetPosition::Disk => self.change_disk_position(1), - WidgetPosition::CpuLegend => self.change_cpu_table_position(1), + match self.current_widget.widget_type { + BottomWidgetType::Proc => self.change_process_position(1), + BottomWidgetType::Temp => self.change_temp_position(1), + BottomWidgetType::Disk => self.change_disk_position(1), + BottomWidgetType::CpuLegend => self.change_cpu_table_position(1), _ => {} } - self.app_scroll_positions.scroll_direction = ScrollDirection::DOWN; self.reset_multi_tap_keys(); } } fn change_cpu_table_position(&mut self, num_to_change_by: i64) { - let current_posn = self - .app_scroll_positions - .cpu_scroll_state - .current_scroll_position; - - let cap = if self.is_filtering_or_searching() { - self.canvas_data.cpu_data.len() as u64 - } else { - self.cpu_state.num_cpus_shown - }; - - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by < cap as i64 + let is_filtering_or_searching = self.is_filtering_or_searching(); + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) { - self.app_scroll_positions - .cpu_scroll_state - .current_scroll_position = (current_posn as i64 + num_to_change_by) as u64; + let current_posn = cpu_widget_state.scroll_state.current_scroll_position; + + let cap = if is_filtering_or_searching { + self.canvas_data.cpu_data.len() + } else { + cpu_widget_state.num_cpus_shown + }; + + if current_posn as i64 + num_to_change_by >= 0 + && current_posn as i64 + num_to_change_by < cap as i64 + { + cpu_widget_state.scroll_state.current_scroll_position = + (current_posn as i64 + num_to_change_by) as u64; + } + + if num_to_change_by < 0 { + cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } else { + cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } } } fn change_process_position(&mut self, num_to_change_by: i64) { - let current_posn = self - .app_scroll_positions - .process_scroll_state - .current_scroll_position; - - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by - < self.canvas_data.finalized_process_data.len() as i64 + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) { - self.app_scroll_positions - .process_scroll_state - .current_scroll_position = (current_posn as i64 + num_to_change_by) as u64; + let current_posn = proc_widget_state.scroll_state.current_scroll_position; + + if let Some(finalized_process_data) = self + .canvas_data + .finalized_process_data_map + .get(&self.current_widget.widget_id) + { + if current_posn as i64 + num_to_change_by >= 0 + && current_posn as i64 + num_to_change_by < finalized_process_data.len() as i64 + { + proc_widget_state.scroll_state.current_scroll_position = + (current_posn as i64 + num_to_change_by) as u64; + } + } + + if num_to_change_by < 0 { + proc_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } else { + proc_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } } } fn change_temp_position(&mut self, num_to_change_by: i64) { - let current_posn = self - .app_scroll_positions - .temp_scroll_state - .current_scroll_position; - - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by - < self.canvas_data.temp_sensor_data.len() as i64 + if let Some(temp_widget_state) = self + .temp_state + .widget_states + .get_mut(&self.current_widget.widget_id) { - self.app_scroll_positions - .temp_scroll_state - .current_scroll_position = (current_posn as i64 + num_to_change_by) as u64; + let current_posn = temp_widget_state.scroll_state.current_scroll_position; + + if current_posn as i64 + num_to_change_by >= 0 + && current_posn as i64 + num_to_change_by + < self.canvas_data.temp_sensor_data.len() as i64 + { + temp_widget_state.scroll_state.current_scroll_position = + (current_posn as i64 + num_to_change_by) as u64; + } + + if num_to_change_by < 0 { + temp_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } else { + temp_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } } } fn change_disk_position(&mut self, num_to_change_by: i64) { - let current_posn = self - .app_scroll_positions - .disk_scroll_state - .current_scroll_position; - - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by < self.canvas_data.disk_data.len() as i64 + if let Some(disk_widget_state) = self + .disk_state + .widget_states + .get_mut(&self.current_widget.widget_id) { - self.app_scroll_positions - .disk_scroll_state - .current_scroll_position = (current_posn as i64 + num_to_change_by) as u64; + let current_posn = disk_widget_state.scroll_state.current_scroll_position; + + if current_posn as i64 + num_to_change_by >= 0 + && current_posn as i64 + num_to_change_by < self.canvas_data.disk_data.len() as i64 + { + disk_widget_state.scroll_state.current_scroll_position = + (current_posn as i64 + num_to_change_by) as u64; + } + + if num_to_change_by < 0 { + disk_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; + } else { + disk_widget_state.scroll_state.scroll_direction = ScrollDirection::DOWN; + } } } pub fn handle_scroll_up(&mut self) { - if self.current_widget_selected.is_widget_graph() { + if self.current_widget.widget_type.is_widget_graph() { self.zoom_in(); - } else if self.current_widget_selected.is_widget_table() { + } else if self.current_widget.widget_type.is_widget_table() { self.decrement_position_count(); } } pub fn handle_scroll_down(&mut self) { - if self.current_widget_selected.is_widget_graph() { + if self.current_widget.widget_type.is_widget_graph() { self.zoom_out(); - } else if self.current_widget_selected.is_widget_table() { + } else if self.current_widget.widget_type.is_widget_table() { self.increment_position_count(); } } fn zoom_out(&mut self) { - match self.current_widget_selected { - WidgetPosition::Cpu => { - let new_time = - self.cpu_state.current_display_time + self.app_config_fields.time_interval; - if new_time <= constants::STALE_MAX_MILLISECONDS { - self.cpu_state.current_display_time = new_time; - self.cpu_state.force_update = true; - if self.app_config_fields.autohide_time { - self.cpu_state.autohide_timer = Some(Instant::now()); - } - } else if self.cpu_state.current_display_time != constants::STALE_MAX_MILLISECONDS { - self.cpu_state.current_display_time = constants::STALE_MAX_MILLISECONDS; - self.cpu_state.force_update = true; - if self.app_config_fields.autohide_time { - self.cpu_state.autohide_timer = Some(Instant::now()); + match self.current_widget.widget_type { + BottomWidgetType::Cpu => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let new_time = cpu_widget_state.current_display_time + + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + cpu_widget_state.current_display_time = new_time; + self.cpu_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + cpu_widget_state.autohide_timer = Some(Instant::now()); + } + } else if cpu_widget_state.current_display_time + != constants::STALE_MAX_MILLISECONDS + { + cpu_widget_state.current_display_time = constants::STALE_MAX_MILLISECONDS; + self.cpu_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + cpu_widget_state.autohide_timer = Some(Instant::now()); + } } } } - WidgetPosition::Mem => { - let new_time = - self.mem_state.current_display_time + self.app_config_fields.time_interval; - if new_time <= constants::STALE_MAX_MILLISECONDS { - self.mem_state.current_display_time = new_time; - self.mem_state.force_update = true; - if self.app_config_fields.autohide_time { - self.mem_state.autohide_timer = Some(Instant::now()); - } - } else if self.mem_state.current_display_time != constants::STALE_MAX_MILLISECONDS { - self.mem_state.current_display_time = constants::STALE_MAX_MILLISECONDS; - self.mem_state.force_update = true; - if self.app_config_fields.autohide_time { - self.mem_state.autohide_timer = Some(Instant::now()); + BottomWidgetType::Mem => { + if let Some(mem_widget_state) = self + .mem_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let new_time = mem_widget_state.current_display_time + + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + mem_widget_state.current_display_time = new_time; + self.mem_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + mem_widget_state.autohide_timer = Some(Instant::now()); + } + } else if mem_widget_state.current_display_time + != constants::STALE_MAX_MILLISECONDS + { + mem_widget_state.current_display_time = constants::STALE_MAX_MILLISECONDS; + self.mem_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + mem_widget_state.autohide_timer = Some(Instant::now()); + } } } } - WidgetPosition::Network => { - let new_time = - self.net_state.current_display_time + self.app_config_fields.time_interval; - if new_time <= constants::STALE_MAX_MILLISECONDS { - self.net_state.current_display_time = new_time; - self.net_state.force_update = true; - if self.app_config_fields.autohide_time { - self.net_state.autohide_timer = Some(Instant::now()); - } - } else if self.net_state.current_display_time != constants::STALE_MAX_MILLISECONDS { - self.net_state.current_display_time = constants::STALE_MAX_MILLISECONDS; - self.net_state.force_update = true; - if self.app_config_fields.autohide_time { - self.net_state.autohide_timer = Some(Instant::now()); + BottomWidgetType::Net => { + if let Some(net_widget_state) = self + .net_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let new_time = net_widget_state.current_display_time + + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + net_widget_state.current_display_time = new_time; + self.net_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + net_widget_state.autohide_timer = Some(Instant::now()); + } + } else if net_widget_state.current_display_time + != constants::STALE_MAX_MILLISECONDS + { + net_widget_state.current_display_time = constants::STALE_MAX_MILLISECONDS; + self.net_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + net_widget_state.autohide_timer = Some(Instant::now()); + } } } } @@ -1582,55 +2255,79 @@ impl App { } fn zoom_in(&mut self) { - match self.current_widget_selected { - WidgetPosition::Cpu => { - let new_time = - self.cpu_state.current_display_time - self.app_config_fields.time_interval; - if new_time >= constants::STALE_MIN_MILLISECONDS { - self.cpu_state.current_display_time = new_time; - self.cpu_state.force_update = true; - if self.app_config_fields.autohide_time { - self.cpu_state.autohide_timer = Some(Instant::now()); - } - } else if self.cpu_state.current_display_time != constants::STALE_MIN_MILLISECONDS { - self.cpu_state.current_display_time = constants::STALE_MIN_MILLISECONDS; - self.cpu_state.force_update = true; - if self.app_config_fields.autohide_time { - self.cpu_state.autohide_timer = Some(Instant::now()); + match self.current_widget.widget_type { + BottomWidgetType::Cpu => { + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let new_time = cpu_widget_state.current_display_time + - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + cpu_widget_state.current_display_time = new_time; + self.cpu_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + cpu_widget_state.autohide_timer = Some(Instant::now()); + } + } else if cpu_widget_state.current_display_time + != constants::STALE_MIN_MILLISECONDS + { + cpu_widget_state.current_display_time = constants::STALE_MIN_MILLISECONDS; + self.cpu_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + cpu_widget_state.autohide_timer = Some(Instant::now()); + } } } } - WidgetPosition::Mem => { - let new_time = - self.mem_state.current_display_time - self.app_config_fields.time_interval; - if new_time >= constants::STALE_MIN_MILLISECONDS { - self.mem_state.current_display_time = new_time; - self.mem_state.force_update = true; - if self.app_config_fields.autohide_time { - self.mem_state.autohide_timer = Some(Instant::now()); - } - } else if self.mem_state.current_display_time != constants::STALE_MIN_MILLISECONDS { - self.mem_state.current_display_time = constants::STALE_MIN_MILLISECONDS; - self.mem_state.force_update = true; - if self.app_config_fields.autohide_time { - self.mem_state.autohide_timer = Some(Instant::now()); + BottomWidgetType::Mem => { + if let Some(mem_widget_state) = self + .mem_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let new_time = mem_widget_state.current_display_time + - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + mem_widget_state.current_display_time = new_time; + self.mem_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + mem_widget_state.autohide_timer = Some(Instant::now()); + } + } else if mem_widget_state.current_display_time + != constants::STALE_MIN_MILLISECONDS + { + mem_widget_state.current_display_time = constants::STALE_MIN_MILLISECONDS; + self.mem_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + mem_widget_state.autohide_timer = Some(Instant::now()); + } } } } - WidgetPosition::Network => { - let new_time = - self.net_state.current_display_time - self.app_config_fields.time_interval; - if new_time >= constants::STALE_MIN_MILLISECONDS { - self.net_state.current_display_time = new_time; - self.net_state.force_update = true; - if self.app_config_fields.autohide_time { - self.net_state.autohide_timer = Some(Instant::now()); - } - } else if self.net_state.current_display_time != constants::STALE_MIN_MILLISECONDS { - self.net_state.current_display_time = constants::STALE_MIN_MILLISECONDS; - self.net_state.force_update = true; - if self.app_config_fields.autohide_time { - self.net_state.autohide_timer = Some(Instant::now()); + BottomWidgetType::Net => { + if let Some(net_widget_state) = self + .net_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let new_time = net_widget_state.current_display_time + - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + net_widget_state.current_display_time = new_time; + self.net_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + net_widget_state.autohide_timer = Some(Instant::now()); + } + } else if net_widget_state.current_display_time + != constants::STALE_MIN_MILLISECONDS + { + net_widget_state.current_display_time = constants::STALE_MIN_MILLISECONDS; + self.net_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + net_widget_state.autohide_timer = Some(Instant::now()); + } } } } @@ -1639,34 +2336,52 @@ impl App { } fn reset_cpu_zoom(&mut self) { - self.cpu_state.current_display_time = self.app_config_fields.default_time_value; - self.cpu_state.force_update = true; - if self.app_config_fields.autohide_time { - self.cpu_state.autohide_timer = Some(Instant::now()); + if let Some(cpu_widget_state) = self + .cpu_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + cpu_widget_state.current_display_time = self.app_config_fields.default_time_value; + self.cpu_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + cpu_widget_state.autohide_timer = Some(Instant::now()); + } } } fn reset_mem_zoom(&mut self) { - self.mem_state.current_display_time = self.app_config_fields.default_time_value; - self.mem_state.force_update = true; - if self.app_config_fields.autohide_time { - self.mem_state.autohide_timer = Some(Instant::now()); + if let Some(mem_widget_state) = self + .mem_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + mem_widget_state.current_display_time = self.app_config_fields.default_time_value; + self.mem_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + mem_widget_state.autohide_timer = Some(Instant::now()); + } } } fn reset_net_zoom(&mut self) { - self.net_state.current_display_time = self.app_config_fields.default_time_value; - self.net_state.force_update = true; - if self.app_config_fields.autohide_time { - self.net_state.autohide_timer = Some(Instant::now()); + if let Some(net_widget_state) = self + .net_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + net_widget_state.current_display_time = self.app_config_fields.default_time_value; + self.net_state.force_update = Some(self.current_widget.widget_id); + if self.app_config_fields.autohide_time { + net_widget_state.autohide_timer = Some(Instant::now()); + } } } fn reset_zoom(&mut self) { - match self.current_widget_selected { - WidgetPosition::Cpu => self.reset_cpu_zoom(), - WidgetPosition::Mem => self.reset_mem_zoom(), - WidgetPosition::Network => self.reset_net_zoom(), + match self.current_widget.widget_type { + BottomWidgetType::Cpu => self.reset_cpu_zoom(), + BottomWidgetType::Mem => self.reset_mem_zoom(), + BottomWidgetType::Net => self.reset_net_zoom(), _ => {} } } diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index fe3fb750..2eb65e05 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -80,8 +80,6 @@ fn cpu_usage_calculation( let total_delta: f64 = total - prev_total; let idle_delta: f64 = idle - *prev_idle; - //debug!("Vangelis function: CPU PERCENT: {}", (total_delta - idle_delta) / total_delta * 100_f64); - *prev_idle = idle; *prev_non_idle = non_idle; @@ -111,8 +109,6 @@ fn get_process_cpu_stats(pid: u32) -> std::io::Result { let utime = val[13].parse::().unwrap_or(0_f64); let stime = val[14].parse::().unwrap_or(0_f64); - //debug!("PID: {}, utime: {}, stime: {}", pid, utime, stime); - Ok(utime + stime) // This seems to match top... } @@ -134,15 +130,6 @@ fn linux_cpu_usage( }; let after_proc_val = get_process_cpu_stats(pid)?; - /*debug!( - "PID - {} - Before: {}, After: {}, CPU: {}, Percentage: {}", - pid, - before_proc_val, - after_proc_val, - cpu_usage, - (after_proc_val - before_proc_val) / cpu_usage * 100_f64 - );*/ - new_pid_stats.insert(pid.to_string(), (after_proc_val, curr_time)); if use_current_cpu_total { diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs new file mode 100644 index 00000000..41ec72b6 --- /dev/null +++ b/src/app/layout_manager.rs @@ -0,0 +1,940 @@ +use crate::error::{BottomError, Result}; +use std::collections::BTreeMap; +use typed_builder::*; + +use crate::constants::DEFAULT_WIDGET_ID; + +/// Represents a more usable representation of the layout, derived from the +/// config. +#[derive(Clone, Debug)] +pub struct BottomLayout { + pub rows: Vec, + pub total_row_height_ratio: u32, +} + +type WidgetMappings = (u32, BTreeMap<(u32, u32), u64>); +type ColumnRowMappings = (u32, BTreeMap<(u32, u32), WidgetMappings>); +type ColumnMappings = (u32, BTreeMap<(u32, u32), ColumnRowMappings>); + +impl BottomLayout { + #[allow(clippy::cognitive_complexity)] + pub fn get_movement_mappings(&mut self) { + fn is_intersecting(a: (u32, u32), b: (u32, u32)) -> bool { + a.0 >= b.0 && a.1 <= b.1 + || a.1 >= b.1 && a.0 <= b.0 + || a.0 <= b.0 && a.1 >= b.0 + || a.0 >= b.0 && a.0 < b.1 && a.1 >= b.1 + } + + fn get_distance(target: (u32, u32), candidate: (u32, u32)) -> u32 { + if candidate.0 < target.0 { + candidate.1 - target.0 + } else if candidate.1 < target.1 { + candidate.1 - candidate.0 + } else { + target.1 - candidate.0 + } + } + + // Now we need to create the correct mapping for moving from a specific + // widget to another + + let mut layout_mapping: BTreeMap<(u32, u32), ColumnMappings> = BTreeMap::new(); + let mut total_height = 0; + for row in &self.rows { + let mut row_width = 0; + let mut row_mapping: BTreeMap<(u32, u32), ColumnRowMappings> = BTreeMap::new(); + let mut is_valid_row = false; + for col in &row.children { + let mut col_row_height = 0; + let mut col_mapping: BTreeMap<(u32, u32), WidgetMappings> = BTreeMap::new(); + let mut is_valid_col = false; + + for col_row in &col.children { + let mut widget_width = 0; + let mut col_row_mapping: BTreeMap<(u32, u32), u64> = BTreeMap::new(); + let mut is_valid_col_row = false; + for widget in &col_row.children { + match widget.widget_type { + BottomWidgetType::Empty => {} + _ => { + is_valid_col_row = true; + col_row_mapping.insert( + ( + widget_width * 100 / col_row.total_widget_ratio, + (widget_width + widget.width_ratio) * 100 + / col_row.total_widget_ratio, + ), + widget.widget_id, + ); + } + } + widget_width += widget.width_ratio; + } + if is_valid_col_row { + col_mapping.insert( + ( + col_row_height * 100 / col.total_col_row_ratio, + (col_row_height + col_row.col_row_height_ratio) * 100 + / col.total_col_row_ratio, + ), + (col.total_col_row_ratio, col_row_mapping), + ); + is_valid_col = true; + } + + col_row_height += col_row.col_row_height_ratio; + } + if is_valid_col { + row_mapping.insert( + ( + row_width * 100 / row.total_col_ratio, + (row_width + col.col_width_ratio) * 100 / row.total_col_ratio, + ), + (row.total_col_ratio, col_mapping), + ); + is_valid_row = true; + } + + row_width += col.col_width_ratio; + } + if is_valid_row { + layout_mapping.insert( + ( + total_height * 100 / self.total_row_height_ratio, + (total_height + row.row_height_ratio) * 100 / self.total_row_height_ratio, + ), + (self.total_row_height_ratio, row_mapping), + ); + } + total_height += row.row_height_ratio; + } + + // Now pass through a second time; this time we want to build up + // our neighbour profile. + let mut height_cursor = 0; + for row in &mut self.rows { + let mut col_cursor = 0; + let row_height_percentage_start = height_cursor * 100 / self.total_row_height_ratio; + let row_height_percentage_end = + (height_cursor + row.row_height_ratio) * 100 / self.total_row_height_ratio; + + for col in &mut row.children { + let mut col_row_cursor = 0; + let col_width_percentage_start = col_cursor * 100 / row.total_col_ratio; + let col_width_percentage_end = + (col_cursor + col.col_width_ratio) * 100 / row.total_col_ratio; + + for col_row in &mut col.children { + let mut widget_cursor = 0; + let col_row_height_percentage_start = + col_row_cursor * 100 / col.total_col_row_ratio; + let col_row_height_percentage_end = + (col_row_cursor + col_row.col_row_height_ratio) * 100 + / col.total_col_row_ratio; + let col_row_children_len = col_row.children.len(); + + for widget in &mut col_row.children { + // Bail if empty. + if let BottomWidgetType::Empty = widget.widget_type { + continue; + } + + let widget_width_percentage_start = + widget_cursor * 100 / col_row.total_widget_ratio; + let widget_width_percentage_end = + (widget_cursor + widget.width_ratio) * 100 / col_row.total_widget_ratio; + + if let Some(current_row) = layout_mapping + .get(&(row_height_percentage_start, row_height_percentage_end)) + { + // First check for within the same col_row for left and right + if let Some(current_col) = current_row + .1 + .get(&(col_width_percentage_start, col_width_percentage_end)) + { + if let Some(current_col_row) = current_col.1.get(&( + col_row_height_percentage_start, + col_row_height_percentage_end, + )) { + if let Some(to_left_widget) = current_col_row + .1 + .range( + ..( + widget_width_percentage_start, + widget_width_percentage_start, + ), + ) + .next_back() + { + widget.left_neighbour = Some(*to_left_widget.1); + } + + // Right + if let Some(to_right_neighbour) = current_col_row + .1 + .range( + ( + widget_width_percentage_end, + widget_width_percentage_end, + ).., + ) + .next() + { + widget.right_neighbour = Some(*to_right_neighbour.1); + } + } + } + + if widget.left_neighbour.is_none() { + if let Some(to_left_col) = current_row + .1 + .range( + ..(col_width_percentage_start, col_width_percentage_start), + ) + .next_back() + { + // Check left in same row + let mut current_best_distance = 0; + let mut current_best_widget_id = widget.widget_id; + + for widget_position in &(to_left_col.1).1 { + let candidate_start = (widget_position.0).0; + let candidate_end = (widget_position.0).1; + + if is_intersecting( + ( + col_row_height_percentage_start, + col_row_height_percentage_end, + ), + (candidate_start, candidate_end), + ) { + let candidate_distance = get_distance( + ( + col_row_height_percentage_start, + col_row_height_percentage_end, + ), + (candidate_start, candidate_end), + ); + + if current_best_distance < candidate_distance { + if let Some(new_best_widget) = + (widget_position.1).1.iter().next_back() + { + current_best_distance = candidate_distance + 1; + current_best_widget_id = *(new_best_widget.1); + } + } + } + } + if current_best_distance > 0 { + widget.left_neighbour = Some(current_best_widget_id); + } + } + } + + if widget.right_neighbour.is_none() { + if let Some(to_right_col) = current_row + .1 + .range((col_width_percentage_end, col_width_percentage_end)..) + .next() + { + // Check right in same row + let mut current_best_distance = 0; + let mut current_best_widget_id = widget.widget_id; + + for widget_position in &(to_right_col.1).1 { + let candidate_start = (widget_position.0).0; + let candidate_end = (widget_position.0).1; + + if is_intersecting( + ( + col_row_height_percentage_start, + col_row_height_percentage_end, + ), + (candidate_start, candidate_end), + ) { + let candidate_distance = get_distance( + ( + col_row_height_percentage_start, + col_row_height_percentage_end, + ), + (candidate_start, candidate_end), + ); + + if current_best_distance < candidate_distance { + if let Some(new_best_widget) = + (widget_position.1).1.iter().next() + { + current_best_distance = candidate_distance + 1; + current_best_widget_id = *(new_best_widget.1); + } + } + } + } + if current_best_distance > 0 { + widget.right_neighbour = Some(current_best_widget_id); + } + } + } + + // Check up/down within same row; + // else check up/down with other rows + if let Some(current_col) = current_row + .1 + .get(&(col_width_percentage_start, col_width_percentage_end)) + { + if let Some(to_up) = current_col + .1 + .range( + ..( + col_row_height_percentage_start, + col_row_height_percentage_start, + ), + ) + .next_back() + { + // Now check each widget_width and pick the best + for candidate_widget in &(to_up.1).1 { + let mut current_best_distance = 0; + let mut current_best_widget_id = widget.widget_id; + if is_intersecting( + ( + widget_width_percentage_start, + widget_width_percentage_end, + ), + ((candidate_widget.0).0, (candidate_widget.0).1), + ) { + let candidate_best_distance = get_distance( + ( + widget_width_percentage_start, + widget_width_percentage_end, + ), + ((candidate_widget.0).0, (candidate_widget.0).1), + ); + + if current_best_distance < candidate_best_distance { + current_best_distance = candidate_best_distance + 1; + current_best_widget_id = *candidate_widget.1; + } + } + + if current_best_distance > 0 { + widget.up_neighbour = Some(current_best_widget_id); + } + } + } else if let Some(next_row_up) = layout_mapping + .range( + ..( + row_height_percentage_start, + row_height_percentage_start, + ), + ) + .next_back() + { + let mut current_best_distance = 0; + let mut current_best_widget_id = widget.widget_id; + let (target_start_width, target_end_width) = + if col_row_children_len > 1 { + ( + col_width_percentage_start + + widget_width_percentage_start + * (col_width_percentage_end + - col_width_percentage_start) + / 100, + col_width_percentage_start + + widget_width_percentage_end + * (col_width_percentage_end + - col_width_percentage_start) + / 100, + ) + } else { + (col_width_percentage_start, col_width_percentage_end) + }; + + for col_position in &(next_row_up.1).1 { + if let Some(next_col_row) = + (col_position.1).1.iter().next_back() + { + let (candidate_col_start, candidate_col_end) = + ((col_position.0).0, (col_position.0).1); + let candidate_difference = + candidate_col_end - candidate_col_start; + for candidate_widget in &(next_col_row.1).1 { + let candidate_start = candidate_col_start + + (candidate_widget.0).0 * candidate_difference + / 100; + let candidate_end = candidate_col_start + + (candidate_widget.0).1 * candidate_difference + / 100; + + if is_intersecting( + (target_start_width, target_end_width), + (candidate_start, candidate_end), + ) { + let candidate_distance = get_distance( + (target_start_width, target_end_width), + (candidate_start, candidate_end), + ); + + if current_best_distance < candidate_distance { + current_best_distance = + candidate_distance + 1; + current_best_widget_id = + *(candidate_widget.1); + } + } + } + } + } + + if current_best_distance > 0 { + widget.up_neighbour = Some(current_best_widget_id); + } + } + + if let Some(to_down) = current_col + .1 + .range( + ( + col_row_height_percentage_start + 1, + col_row_height_percentage_start + 1, + ).., + ) + .next() + { + for candidate_widget in &(to_down.1).1 { + let mut current_best_distance = 0; + let mut current_best_widget_id = widget.widget_id; + if is_intersecting( + ( + widget_width_percentage_start, + widget_width_percentage_end, + ), + ((candidate_widget.0).0, (candidate_widget.0).1), + ) { + let candidate_best_distance = get_distance( + ( + widget_width_percentage_start, + widget_width_percentage_end, + ), + ((candidate_widget.0).0, (candidate_widget.0).1), + ); + + if current_best_distance < candidate_best_distance { + current_best_distance = candidate_best_distance + 1; + current_best_widget_id = *candidate_widget.1; + } + } + + if current_best_distance > 0 { + widget.down_neighbour = Some(current_best_widget_id); + } + } + } else if let Some(next_row_down) = layout_mapping + .range( + ( + row_height_percentage_start + 1, + row_height_percentage_start + 1, + ).., + ) + .next() + { + let mut current_best_distance = 0; + let mut current_best_widget_id = widget.widget_id; + let (target_start_width, target_end_width) = + if col_row_children_len > 1 { + ( + col_width_percentage_start + + widget_width_percentage_start + * (col_width_percentage_end + - col_width_percentage_start) + / 100, + col_width_percentage_start + + widget_width_percentage_end + * (col_width_percentage_end + - col_width_percentage_start) + / 100, + ) + } else { + (col_width_percentage_start, col_width_percentage_end) + }; + + for col_position in &(next_row_down.1).1 { + if let Some(next_col_row) = (col_position.1).1.iter().next() + { + let (candidate_col_start, candidate_col_end) = + ((col_position.0).0, (col_position.0).1); + let candidate_difference = + candidate_col_end - candidate_col_start; + for candidate_widget in &(next_col_row.1).1 { + let candidate_start = candidate_col_start + + (candidate_widget.0).0 * candidate_difference + / 100; + let candidate_end = candidate_col_start + + (candidate_widget.0).1 * candidate_difference + / 100; + + if is_intersecting( + (target_start_width, target_end_width), + (candidate_start, candidate_end), + ) { + let candidate_distance = get_distance( + (target_start_width, target_end_width), + (candidate_start, candidate_end), + ); + + if current_best_distance < candidate_distance { + current_best_distance = + candidate_distance + 1; + current_best_widget_id = + *(candidate_widget.1); + } + } + } + } + } + + if current_best_distance > 0 { + widget.down_neighbour = Some(current_best_widget_id); + } + } + } + } + widget_cursor += widget.width_ratio; + } + col_row_cursor += col_row.col_row_height_ratio; + } + col_cursor += col.col_width_ratio; + } + height_cursor += row.row_height_ratio; + } + } + + pub fn init_basic_default() -> Self { + BottomLayout { + total_row_height_ratio: 3, + rows: vec![ + BottomRow::builder() + .canvas_handle_height(true) + .children(vec![BottomCol::builder() + .canvas_handle_width(true) + .children(vec![BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::BasicCpu) + .widget_id(1) + .down_neighbour(Some(2)) + .build()]) + .build()]) + .build()]) + .build(), + BottomRow::builder() + .canvas_handle_height(true) + .children(vec![BottomCol::builder() + .canvas_handle_width(true) + .children(vec![BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![ + BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::BasicMem) + .widget_id(2) + .up_neighbour(Some(1)) + .down_neighbour(Some(100)) + .right_neighbour(Some(3)) + .build(), + BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::BasicNet) + .widget_id(3) + .up_neighbour(Some(1)) + .down_neighbour(Some(100)) + .left_neighbour(Some(2)) + .build(), + ]) + .build()]) + .build()]) + .build(), + BottomRow::builder() + .canvas_handle_height(true) + .children(vec![BottomCol::builder() + .canvas_handle_width(true) + .children(vec![BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::BasicTables) + .widget_id(100) + .up_neighbour(Some(2)) + .build()]) + .build()]) + .build()]) + .build(), + BottomRow::builder() + .canvas_handle_height(true) + .children(vec![ + BottomCol::builder() + .canvas_handle_width(true) + .children(vec![BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::Disk) + .widget_id(4) + .up_neighbour(Some(100)) + .left_neighbour(Some(7)) + .right_neighbour(Some(DEFAULT_WIDGET_ID)) + .build()]) + .build()]) + .build(), + BottomCol::builder() + .canvas_handle_width(true) + .children(vec![ + BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::Proc) + .widget_id(DEFAULT_WIDGET_ID) + .up_neighbour(Some(100)) + .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) + .left_neighbour(Some(4)) + .right_neighbour(Some(7)) + .build()]) + .build(), + BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::ProcSearch) + .widget_id(DEFAULT_WIDGET_ID + 1) + .up_neighbour(Some(DEFAULT_WIDGET_ID)) + .left_neighbour(Some(4)) + .right_neighbour(Some(7)) + .build()]) + .build(), + ]) + .build(), + BottomCol::builder() + .canvas_handle_width(true) + .children(vec![BottomColRow::builder() + .canvas_handle_height(true) + .children(vec![BottomWidget::builder() + .canvas_handle_width(true) + .widget_type(BottomWidgetType::Temp) + .widget_id(7) + .up_neighbour(Some(100)) + .left_neighbour(Some(DEFAULT_WIDGET_ID)) + .right_neighbour(Some(4)) + .build()]) + .build()]) + .build(), + ]) + .build(), + ], + } + } + + pub fn init_default(left_legend: bool) -> Self { + BottomLayout { + total_row_height_ratio: 100, + rows: vec![ + BottomRow::builder() + .row_height_ratio(30) + .children(vec![BottomCol::builder() + .children(vec![BottomColRow::builder() + .total_widget_ratio(20) + .children(if left_legend { + vec![ + BottomWidget::builder() + .width_ratio(3) + .widget_type(BottomWidgetType::CpuLegend) + .widget_id(2) + .down_neighbour(Some(11)) + .right_neighbour(Some(1)) + .canvas_handle_width(true) + .build(), + BottomWidget::builder() + .width_ratio(17) + .widget_type(BottomWidgetType::Cpu) + .widget_id(1) + .down_neighbour(Some(12)) + .left_neighbour(Some(2)) + .flex_grow(true) + .build(), + ] + } else { + vec![ + BottomWidget::builder() + .width_ratio(17) + .widget_type(BottomWidgetType::Cpu) + .widget_id(1) + .down_neighbour(Some(11)) + .right_neighbour(Some(2)) + .flex_grow(true) + .build(), + BottomWidget::builder() + .width_ratio(3) + .widget_type(BottomWidgetType::CpuLegend) + .widget_id(2) + .down_neighbour(Some(12)) + .left_neighbour(Some(1)) + .canvas_handle_width(true) + .build(), + ] + }) + .build()]) + .build()]) + .build(), + BottomRow::builder() + .total_col_ratio(7) + .row_height_ratio(40) + .children(vec![ + BottomCol::builder() + .col_width_ratio(4) + .children(vec![BottomColRow::builder() + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Mem) + .widget_id(11) + .right_neighbour(Some(12)) + .up_neighbour(Some(1)) + .down_neighbour(Some(21)) + .build()]) + .build()]) + .build(), + BottomCol::builder() + .total_col_row_ratio(2) + .col_width_ratio(3) + .children(vec![ + BottomColRow::builder() + .col_row_height_ratio(1) + .total_widget_ratio(2) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Temp) + .widget_id(12) + .left_neighbour(Some(11)) + .up_neighbour(Some(1)) + .down_neighbour(Some(13)) + .build()]) + .build(), + BottomColRow::builder() + .col_row_height_ratio(1) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Disk) + .widget_id(13) + .left_neighbour(Some(11)) + .up_neighbour(Some(12)) + .down_neighbour(Some(DEFAULT_WIDGET_ID)) + .build()]) + .build(), + ]) + .build(), + ]) + .build(), + BottomRow::builder() + .total_col_ratio(2) + .row_height_ratio(30) + .children(vec![ + BottomCol::builder() + .children(vec![BottomColRow::builder() + .col_row_height_ratio(1) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Net) + .widget_id(21) + .right_neighbour(Some(DEFAULT_WIDGET_ID)) + .up_neighbour(Some(11)) + .build()]) + .build()]) + .build(), + BottomCol::builder() + .total_col_row_ratio(2) + .children(vec![ + BottomColRow::builder() + .col_row_height_ratio(1) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Proc) + .widget_id(DEFAULT_WIDGET_ID) + .left_neighbour(Some(21)) + .up_neighbour(Some(13)) + .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) + .build()]) + .flex_grow(true) + .build(), + BottomColRow::builder() + .col_row_height_ratio(1) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::ProcSearch) + .widget_id(DEFAULT_WIDGET_ID + 1) + .up_neighbour(Some(DEFAULT_WIDGET_ID)) + .left_neighbour(Some(21)) + .build()]) + .canvas_handle_height(true) + .build(), + ]) + .build(), + ]) + .build(), + ], + } + } +} + +/// Represents a single row in the layout. +#[derive(Clone, Debug, TypedBuilder)] +pub struct BottomRow { + pub children: Vec, + + #[builder(default = 1)] + pub total_col_ratio: u32, + + #[builder(default = 1)] + pub row_height_ratio: u32, + + #[builder(default = false)] + pub canvas_handle_height: bool, + + #[builder(default = false)] + pub flex_grow: bool, +} + +/// Represents a single column in the layout. We assume that even if the column +/// contains only ONE element, it is still a column (rather than either a col or +/// a widget, as per the config, for simplicity's sake). +#[derive(Clone, Debug, TypedBuilder)] +pub struct BottomCol { + pub children: Vec, + + #[builder(default = 1)] + pub total_col_row_ratio: u32, + + #[builder(default = 1)] + pub col_width_ratio: u32, + + #[builder(default = false)] + pub canvas_handle_width: bool, + + #[builder(default = false)] + pub flex_grow: bool, +} + +#[derive(Clone, Default, Debug, TypedBuilder)] +pub struct BottomColRow { + pub children: Vec, + + #[builder(default = 1)] + pub total_widget_ratio: u32, + + #[builder(default = 1)] + pub col_row_height_ratio: u32, + + #[builder(default = false)] + pub canvas_handle_height: bool, + + #[builder(default = false)] + pub flex_grow: bool, +} + +/// Represents a single widget. +#[derive(Debug, Default, Clone, TypedBuilder)] +pub struct BottomWidget { + pub widget_type: BottomWidgetType, + pub widget_id: u64, + + #[builder(default = 1)] + pub width_ratio: u32, + + #[builder(default = None)] + pub left_neighbour: Option, + + #[builder(default = None)] + pub right_neighbour: Option, + + #[builder(default = None)] + pub up_neighbour: Option, + + #[builder(default = None)] + pub down_neighbour: Option, + + #[builder(default = false)] + pub canvas_handle_width: bool, + + #[builder(default = false)] + pub flex_grow: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum BottomWidgetType { + Empty, + Cpu, + CpuLegend, + Mem, + Net, + Proc, + ProcSearch, + Temp, + Disk, + BasicCpu, + BasicMem, + BasicNet, + BasicTables, +} + +impl BottomWidgetType { + pub fn is_widget_table(&self) -> bool { + use BottomWidgetType::*; + match self { + Disk | Proc | Temp | CpuLegend => true, + _ => false, + } + } + + pub fn is_widget_graph(&self) -> bool { + use BottomWidgetType::*; + match self { + Cpu | Net | Mem => true, + _ => false, + } + } + + pub fn get_pretty_name(&self) -> &str { + use BottomWidgetType::*; + match self { + Cpu => "CPU", + Mem => "Memory", + Net => "Network", + Proc => "Processes", + Temp => "Temperature", + Disk => "Disks", + _ => "", + } + } +} + +impl Default for BottomWidgetType { + fn default() -> Self { + BottomWidgetType::Empty + } +} + +impl std::str::FromStr for BottomWidgetType { + type Err = BottomError; + + fn from_str(s: &str) -> Result { + let lower_case = s.to_lowercase(); + match lower_case.as_str() { + "cpu" => Ok(BottomWidgetType::Cpu), + "mem" => Ok(BottomWidgetType::Mem), + "net" => Ok(BottomWidgetType::Net), + "proc" => Ok(BottomWidgetType::Proc), + "temp" => Ok(BottomWidgetType::Temp), + "disk" => Ok(BottomWidgetType::Disk), + "empty" => Ok(BottomWidgetType::Empty), + _ => Err(BottomError::ConfigError(format!( + "Invalid widget type: {}", + s + ))), + } + } +} diff --git a/src/app/process_killer.rs b/src/app/process_killer.rs index db284100..05faf945 100644 --- a/src/app/process_killer.rs +++ b/src/app/process_killer.rs @@ -21,7 +21,7 @@ impl Process { fn open(pid: DWORD) -> Result { let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) }; if pc.is_null() { - return Err("!OpenProcess".to_string()); + return Err("OpenProcess".to_string()); } Ok(Process(pc)) } diff --git a/src/canvas.rs b/src/canvas.rs index 8aaa1752..8244e1d6 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -3,8 +3,7 @@ use std::collections::HashMap; use tui::{ backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - terminal::Frame, + layout::{Constraint, Direction, Layout}, widgets::Text, Terminal, }; @@ -14,7 +13,11 @@ use dialogs::*; use widgets::*; use crate::{ - app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition}, + app::{ + self, + data_harvester::processes::ProcessHarvest, + layout_manager::{BottomLayout, BottomWidgetType}, + }, constants::*, data_conversion::{ConvertedCpuData, ConvertedProcessData}, utils::error, @@ -40,7 +43,7 @@ pub struct DisplayableData { // Not the final value pub grouped_process_data: Vec, // What's actually displayed - pub finalized_process_data: Vec, + pub finalized_process_data_map: HashMap>, pub mem_label: String, pub swap_label: String, pub mem_data: Vec<(f64, f64)>, @@ -48,31 +51,110 @@ pub struct DisplayableData { pub cpu_data: Vec, } -#[allow(dead_code)] -#[derive(Default)] /// Handles the canvas' state. TODO: [OPT] implement this. pub struct Painter { + pub colours: CanvasColours, height: u16, width: u16, - vertical_dialog_chunk: Vec, - middle_dialog_chunk: Vec, - vertical_chunks: Vec, - middle_chunks: Vec, - middle_divided_chunk_2: Vec, - bottom_chunks: Vec, - cpu_chunk: Vec, - network_chunk: Vec, - pub colours: CanvasColours, - pub styled_general_help_text: Vec>, - pub styled_process_help_text: Vec>, - pub styled_search_help_text: Vec>, + styled_general_help_text: Vec>, + styled_process_help_text: Vec>, + styled_search_help_text: Vec>, is_mac_os: bool, + row_constraints: Vec, + col_constraints: Vec>, + col_row_constraints: Vec>>, + layout_constraints: Vec>>>, + widget_layout: BottomLayout, } impl Painter { + pub fn init(widget_layout: BottomLayout) -> Self { + // Now for modularity; we have to also initialize the base layouts! + // We want to do this ONCE and reuse; after this we can just construct + // based on the console size. + + let mut row_constraints = Vec::new(); + let mut col_constraints = Vec::new(); + let mut col_row_constraints = Vec::new(); + let mut layout_constraints = Vec::new(); + + widget_layout.rows.iter().for_each(|row| { + if row.canvas_handle_height { + row_constraints.push(Constraint::Length(0)); + } else { + row_constraints.push(Constraint::Ratio( + row.row_height_ratio, + widget_layout.total_row_height_ratio, + )); + } + + let mut new_col_constraints = Vec::new(); + let mut new_widget_constraints = Vec::new(); + let mut new_col_row_constraints = Vec::new(); + row.children.iter().for_each(|col| { + if col.canvas_handle_width { + new_col_constraints.push(Constraint::Length(0)); + } else { + new_col_constraints + .push(Constraint::Ratio(col.col_width_ratio, row.total_col_ratio)); + } + + let mut new_new_col_row_constraints = Vec::new(); + let mut new_new_widget_constraints = Vec::new(); + col.children.iter().for_each(|col_row| { + if col_row.canvas_handle_height { + new_new_col_row_constraints.push(Constraint::Length(0)); + } else if col_row.flex_grow { + new_new_col_row_constraints.push(Constraint::Min(0)); + } else { + new_new_col_row_constraints.push(Constraint::Ratio( + col_row.col_row_height_ratio, + col.total_col_row_ratio, + )); + } + + let mut new_new_new_widget_constraints = Vec::new(); + col_row.children.iter().for_each(|widget| { + if widget.canvas_handle_width { + new_new_new_widget_constraints.push(Constraint::Length(0)); + } else if widget.flex_grow { + new_new_new_widget_constraints.push(Constraint::Min(0)); + } else { + new_new_new_widget_constraints.push(Constraint::Ratio( + widget.width_ratio, + col_row.total_widget_ratio, + )); + } + }); + new_new_widget_constraints.push(new_new_new_widget_constraints); + }); + new_col_row_constraints.push(new_new_col_row_constraints); + new_widget_constraints.push(new_new_widget_constraints); + }); + col_row_constraints.push(new_col_row_constraints); + layout_constraints.push(new_widget_constraints); + col_constraints.push(new_col_constraints); + }); + + Painter { + colours: CanvasColours::default(), + height: 0, + width: 0, + styled_general_help_text: Vec::new(), + styled_process_help_text: Vec::new(), + styled_search_help_text: Vec::new(), + is_mac_os: false, + row_constraints, + col_constraints, + col_row_constraints, + layout_constraints, + widget_layout, + } + } + /// Must be run once before drawing, but after setting colours. /// This is to set some remaining styles and text. - pub fn initialize(&mut self) { + pub fn complete_painter_init(&mut self) { self.is_mac_os = cfg!(target_os = "macos"); if GENERAL_HELP_TEXT.len() > 1 { @@ -115,24 +197,26 @@ impl Painter { } } - pub fn draw_specific_table( - &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, - widget_selected: WidgetPosition, - ) { - match widget_selected { - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - self.draw_process_and_search(f, app_state, draw_loc, draw_border) - } - WidgetPosition::Temp => self.draw_temp_table(f, app_state, draw_loc, draw_border), - WidgetPosition::Disk => self.draw_disk_table(f, app_state, draw_loc, draw_border), - _ => {} - } - } + // pub fn draw_specific_table( + // &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, + // widget_selected: WidgetPosition, + // ) { + // match widget_selected { + // WidgetPosition::Process | WidgetPosition::ProcessSearch => { + // self.draw_process_and_search(f, app_state, draw_loc, draw_border) + // } + // WidgetPosition::Temp => self.draw_temp_table(f, app_state, draw_loc, draw_border), + // WidgetPosition::Disk => self.draw_disk_table(f, app_state, draw_loc, draw_border), + // _ => {} + // } + // } // TODO: [FEATURE] Auto-resizing dialog sizes. pub fn draw_data( &mut self, terminal: &mut Terminal, app_state: &mut app::App, ) -> error::Result<()> { + use BottomWidgetType::*; + let terminal_size = terminal.size()?; let current_height = terminal_size.height; let current_width = terminal_size.width; @@ -231,55 +315,63 @@ impl Painter { } } else if app_state.is_expanded { let rect = Layout::default() - .margin(1) + .margin(0) .constraints([Constraint::Percentage(100)].as_ref()) .split(f.size()); - match &app_state.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::BasicCpu | WidgetPosition::CpuLegend => { - let cpu_chunk = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints( - if app_state.app_config_fields.left_legend { - [Constraint::Percentage(15), Constraint::Percentage(85)] - } else { - [Constraint::Percentage(85), Constraint::Percentage(15)] - } - .as_ref(), - ) - .split(rect[0]); - - let legend_index = if app_state.app_config_fields.left_legend { - 0 - } else { - 1 - }; - let graph_index = if app_state.app_config_fields.left_legend { - 1 - } else { - 0 - }; - - self.draw_cpu_graph(&mut f, app_state, cpu_chunk[graph_index]); - self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]); - } - WidgetPosition::Mem | WidgetPosition::BasicMem => { - self.draw_memory_graph(&mut f, app_state, rect[0]); - } - WidgetPosition::Disk => { - self.draw_disk_table(&mut f, app_state, rect[0], true); - } - WidgetPosition::Temp => { - self.draw_temp_table(&mut f, app_state, rect[0], true); - } - WidgetPosition::Network - | WidgetPosition::BasicNet - | WidgetPosition::NetworkLegend => { - self.draw_network_graph(&mut f, app_state, rect[0]); - } - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - self.draw_process_and_search(&mut f, app_state, rect[0], true); - } + match &app_state.current_widget.widget_type { + Cpu => self.draw_cpu( + &mut f, + app_state, + rect[0], + app_state.current_widget.widget_id, + ), + CpuLegend => self.draw_cpu( + &mut f, + app_state, + rect[0], + app_state.current_widget.widget_id - 1, + ), + Mem | BasicMem => self.draw_memory_graph( + &mut f, + app_state, + rect[0], + app_state.current_widget.widget_id, + ), + Disk => self.draw_disk_table( + &mut f, + app_state, + rect[0], + true, + app_state.current_widget.widget_id, + ), + Temp => self.draw_temp_table( + &mut f, + app_state, + rect[0], + true, + app_state.current_widget.widget_id, + ), + Net => self.draw_network_graph( + &mut f, + app_state, + rect[0], + app_state.current_widget.widget_id, + ), + Proc => self.draw_process_and_search( + &mut f, + app_state, + rect[0], + true, + app_state.current_widget.widget_id, + ), + ProcSearch => self.draw_process_and_search( + &mut f, + app_state, + rect[0], + true, + app_state.current_widget.widget_id - 1, + ), + _ => {} } } else if app_state.app_config_fields.use_basic_mode { // Basic mode. This basically removes all graphs but otherwise @@ -309,112 +401,144 @@ impl Painter { .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(vertical_chunks[2]); - self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0]); - self.draw_basic_memory(&mut f, app_state, middle_chunks[0]); - self.draw_basic_network(&mut f, app_state, middle_chunks[1]); + self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0], 1); + self.draw_basic_memory(&mut f, app_state, middle_chunks[0], 2); + self.draw_basic_network(&mut f, app_state, middle_chunks[1], 3); self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3]); - if app_state.current_widget_selected.is_widget_table() { - self.draw_specific_table( - &mut f, - app_state, - vertical_chunks[4], - false, - app_state.current_widget_selected, - ); - } else { - self.draw_specific_table( - &mut f, - app_state, - vertical_chunks[4], - false, - app_state.previous_basic_table_selected, - ); + if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state { + let widget_id = basic_table_widget_state.currently_displayed_widget_id; + match basic_table_widget_state.currently_displayed_widget_type { + Disk => self.draw_disk_table( + &mut f, + app_state, + vertical_chunks[4], + false, + widget_id, + ), + Proc => self.draw_process_and_search( + &mut f, + app_state, + vertical_chunks[4], + false, + widget_id, + ), + Temp => self.draw_temp_table( + &mut f, + app_state, + vertical_chunks[4], + false, + widget_id, + ), + _ => {} + } } } else { - let vertical_chunks = Layout::default() + // Draws using the passed in (or default) layout. NOT basic so far. + let row_draw_locs = Layout::default() + .margin(0) + .constraints(self.row_constraints.as_ref()) .direction(Direction::Vertical) - .margin(1) - .constraints( - [ - Constraint::Percentage(30), - Constraint::Percentage(37), - Constraint::Percentage(33), - ] - .as_ref(), - ) .split(f.size()); + let col_draw_locs = self + .col_constraints + .iter() + .enumerate() + .map(|(itx, col_constraint)| { + Layout::default() + .constraints(col_constraint.as_ref()) + .direction(Direction::Horizontal) + .split(row_draw_locs[itx]) + }) + .collect::>(); + let col_row_draw_locs = self + .col_row_constraints + .iter() + .enumerate() + .map(|(col_itx, col_row_constraints)| { + col_row_constraints + .iter() + .enumerate() + .map(|(itx, col_row_constraint)| { + Layout::default() + .constraints(col_row_constraint.as_ref()) + .direction(Direction::Vertical) + .split(col_draw_locs[col_itx][itx]) + }) + .collect::>() + }) + .collect::>(); - let middle_chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Percentage(60), Constraint::Percentage(40)].as_ref()) - .split(vertical_chunks[1]); + // Now... draw! + self.layout_constraints.iter().enumerate().for_each( + |(row_itx, col_constraint_vec)| { + col_constraint_vec.iter().enumerate().for_each( + |(col_itx, col_row_constraint_vec)| { + col_row_constraint_vec.iter().enumerate().for_each( + |(col_row_itx, widget_constraints)| { + let widget_draw_locs = Layout::default() + .constraints(widget_constraints.as_ref()) + .direction(Direction::Horizontal) + .split( + col_row_draw_locs[row_itx][col_itx][col_row_itx], + ); - let middle_divided_chunk_2 = Layout::default() - .direction(Direction::Vertical) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(middle_chunks[1]); - - let bottom_chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(vertical_chunks[2]); - - // Component specific chunks - let cpu_chunk = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints( - if app_state.app_config_fields.left_legend { - [Constraint::Percentage(15), Constraint::Percentage(85)] - } else { - [Constraint::Percentage(85), Constraint::Percentage(15)] - } - .as_ref(), - ) - .split(vertical_chunks[0]); - - let network_chunk = Layout::default() - .direction(Direction::Vertical) - .margin(0) - .constraints( - if (bottom_chunks[0].height as f64 * 0.25) as u16 >= 4 { - [Constraint::Percentage(75), Constraint::Percentage(25)] - } else { - let required = if bottom_chunks[0].height < 10 { - bottom_chunks[0].height / 2 - } else { - 5 - }; - let remaining = bottom_chunks[0].height - required; - [Constraint::Length(remaining), Constraint::Length(required)] - } - .as_ref(), - ) - .split(bottom_chunks[0]); - - // Default chunk index based on left or right legend setting - let legend_index = if app_state.app_config_fields.left_legend { - 0 - } else { - 1 - }; - let graph_index = if app_state.app_config_fields.left_legend { - 1 - } else { - 0 - }; - - self.draw_cpu_graph(&mut f, app_state, cpu_chunk[graph_index]); - self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]); - self.draw_memory_graph(&mut f, app_state, middle_chunks[0]); - self.draw_network_graph(&mut f, app_state, network_chunk[0]); - self.draw_network_labels(&mut f, app_state, network_chunk[1]); - self.draw_temp_table(&mut f, app_state, middle_divided_chunk_2[0], true); - self.draw_disk_table(&mut f, app_state, middle_divided_chunk_2[1], true); - self.draw_process_and_search(&mut f, app_state, bottom_chunks[1], true); + for (widget_itx, widget) in self.widget_layout.rows[row_itx] + .children[col_itx] + .children[col_row_itx] + .children + .iter() + .enumerate() + { + match widget.widget_type { + Empty => {} + Cpu => self.draw_cpu( + &mut f, + app_state, + widget_draw_locs[widget_itx], + widget.widget_id, + ), + Mem => self.draw_memory_graph( + &mut f, + app_state, + widget_draw_locs[widget_itx], + widget.widget_id, + ), + Net => self.draw_network( + &mut f, + app_state, + widget_draw_locs[widget_itx], + widget.widget_id, + ), + Temp => self.draw_temp_table( + &mut f, + app_state, + widget_draw_locs[widget_itx], + true, + widget.widget_id, + ), + Disk => self.draw_disk_table( + &mut f, + app_state, + widget_draw_locs[widget_itx], + true, + widget.widget_id, + ), + Proc => self.draw_process_and_search( + &mut f, + app_state, + widget_draw_locs[widget_itx], + true, + widget.widget_id, + ), + _ => {} + } + } + }, + ); + }, + ); + }, + ); } })?; diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index f3ad3274..f73f1cea 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -27,7 +27,7 @@ impl KillDialog for Painter { if let Some(to_kill_processes) = app_state.get_to_delete_processes() { if let Some(first_pid) = to_kill_processes.1.first() { let dd_text = vec![ - if app_state.is_grouped() { + if app_state.is_grouped(app_state.current_widget.widget_id) { if to_kill_processes.1.len() != 1 { Text::raw(format!( "\nKill {} processes with the name {}?", diff --git a/src/canvas/widgets/basic_table_arrows.rs b/src/canvas/widgets/basic_table_arrows.rs index ec6b6515..05392fb4 100644 --- a/src/canvas/widgets/basic_table_arrows.rs +++ b/src/canvas/widgets/basic_table_arrows.rs @@ -1,7 +1,7 @@ use std::cmp::max; use crate::{ - app::{App, WidgetPosition}, + app::{layout_manager::BottomWidgetType, App}, canvas::Painter, }; @@ -23,27 +23,19 @@ impl BasicTableArrows for Painter { &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, ) { // Effectively a paragraph with a ton of spacing - - // TODO: [MODULARITY] This is hard coded. Gross. - let (left_table, right_table) = if app_state.current_widget_selected.is_widget_table() { - match app_state.current_widget_selected { - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - (WidgetPosition::Temp, WidgetPosition::Disk) + let (left_table, right_table) = + if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state { + match basic_table_widget_state.currently_displayed_widget_type { + BottomWidgetType::Proc | BottomWidgetType::ProcSearch => { + (BottomWidgetType::Temp, BottomWidgetType::Disk) + } + BottomWidgetType::Disk => (BottomWidgetType::Proc, BottomWidgetType::Temp), + BottomWidgetType::Temp => (BottomWidgetType::Disk, BottomWidgetType::Proc), + _ => (BottomWidgetType::Disk, BottomWidgetType::Temp), } - WidgetPosition::Disk => (WidgetPosition::Process, WidgetPosition::Temp), - WidgetPosition::Temp => (WidgetPosition::Disk, WidgetPosition::Process), - _ => (WidgetPosition::Disk, WidgetPosition::Temp), - } - } else { - match app_state.previous_basic_table_selected { - WidgetPosition::Process | WidgetPosition::ProcessSearch => { - (WidgetPosition::Temp, WidgetPosition::Disk) - } - WidgetPosition::Disk => (WidgetPosition::Process, WidgetPosition::Temp), - WidgetPosition::Temp => (WidgetPosition::Disk, WidgetPosition::Process), - _ => (WidgetPosition::Disk, WidgetPosition::Temp), - } - }; + } else { + (BottomWidgetType::Disk, BottomWidgetType::Temp) + }; let left_name = left_table.get_pretty_name(); let right_name = right_table.get_pretty_name(); diff --git a/src/canvas/widgets/cpu_basic.rs b/src/canvas/widgets/cpu_basic.rs index 464d8423..3f674b93 100644 --- a/src/canvas/widgets/cpu_basic.rs +++ b/src/canvas/widgets/cpu_basic.rs @@ -1,7 +1,7 @@ use std::cmp::{max, min}; use crate::{ - app::{App, WidgetPosition}, + app::App, canvas::{drawing_utils::*, Painter}, constants::*, data_conversion::ConvertedCpuData, @@ -15,12 +15,14 @@ use tui::{ }; pub trait CpuBasicWidget { - fn draw_basic_cpu(&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect); + fn draw_basic_cpu( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + ); } impl CpuBasicWidget for Painter { fn draw_basic_cpu( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; @@ -34,7 +36,7 @@ impl CpuBasicWidget for Painter { // Then, from this, split the row space across ALL columns. From there, generate // the desired lengths. - if let WidgetPosition::BasicCpu = app_state.current_widget_selected { + if app_state.current_widget.widget_id == widget_id { Block::default() .borders(*SIDE_BORDERS) .border_style(self.colours.highlighted_border_style) diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 19abd62c..6fa0535a 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, WidgetPosition}, + app::App, canvas::{ drawing_utils::{get_start_position, get_variable_intrinsic_widths}, Painter, @@ -14,7 +14,7 @@ use crate::{ use tui::{ backend::Backend, - layout::{Constraint, Rect}, + layout::{Constraint, Direction, Layout, Rect}, terminal::Frame, widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, }; @@ -33,259 +33,307 @@ lazy_static! { } pub trait CpuGraphWidget { - fn draw_cpu_graph(&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect); + fn draw_cpu( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + ); + fn draw_cpu_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + ); fn draw_cpu_legend( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ); } impl CpuGraphWidget for Painter { - fn draw_cpu_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + fn draw_cpu( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { - let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; + if draw_loc.width as f64 * 0.15 <= 6.0 { + // 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(); + } else { + app_state.move_widget_selection_left(); + } + } + self.draw_cpu_graph(f, app_state, draw_loc, widget_id); + if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) { + cpu_widget_state.is_legend_hidden = true; + } + } else { + let (graph_index, legend_index, constraints) = + if app_state.app_config_fields.left_legend { + ( + 1, + 0, + [Constraint::Percentage(15), Constraint::Percentage(85)], + ) + } else { + ( + 0, + 1, + [Constraint::Percentage(85), Constraint::Percentage(15)], + ) + }; - let display_time_labels = [ - format!("{}s", app_state.cpu_state.current_display_time / 1000), - "0s".to_string(), - ]; + let partitioned_draw_loc = Layout::default() + .margin(0) + .direction(Direction::Horizontal) + .constraints(constraints.as_ref()) + .split(draw_loc); - let x_axis = if app_state.app_config_fields.hide_time - || (app_state.app_config_fields.autohide_time - && app_state.cpu_state.autohide_timer.is_none()) - { - Axis::default().bounds([0.0, app_state.cpu_state.current_display_time as f64]) - } else if let Some(time) = app_state.cpu_state.autohide_timer { - if std::time::Instant::now().duration_since(time).as_millis() - < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + self.draw_cpu_graph(f, app_state, partitioned_draw_loc[graph_index], widget_id); + self.draw_cpu_legend( + f, + app_state, + partitioned_draw_loc[legend_index], + widget_id + 1, + ); + } + } + + fn draw_cpu_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + ) { + if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) { + let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data; + + let display_time_labels = [ + format!("{}s", cpu_widget_state.current_display_time / 1000), + "0s".to_string(), + ]; + + let x_axis = if app_state.app_config_fields.hide_time + || (app_state.app_config_fields.autohide_time + && cpu_widget_state.autohide_timer.is_none()) { + Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0]) + } else if let Some(time) = cpu_widget_state.autohide_timer { + if std::time::Instant::now().duration_since(time).as_millis() + < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + { + Axis::default() + .bounds([-(cpu_widget_state.current_display_time as f64), 0.0]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + } else { + cpu_widget_state.autohide_timer = None; + Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0]) + } + } else { Axis::default() - .bounds([0.0, app_state.cpu_state.current_display_time as f64]) + .bounds([-(cpu_widget_state.current_display_time as f64), 0.0]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) - } else { - app_state.cpu_state.autohide_timer = None; - Axis::default().bounds([0.0, app_state.cpu_state.current_display_time as f64]) - } - } else { - Axis::default() - .bounds([0.0, app_state.cpu_state.current_display_time as f64]) + }; + + // Note this is offset as otherwise the 0 value is not drawn! + let y_axis = Axis::default() .style(self.colours.graph_style) .labels_style(self.colours.graph_style) - .labels(&display_time_labels) - }; + .bounds([-0.5, 100.5]) + .labels(&["0%", "100%"]); - // Note this is offset as otherwise the 0 value is not drawn! - let y_axis = Axis::default() - .style(self.colours.graph_style) - .labels_style(self.colours.graph_style) - .bounds([-0.5, 100.5]) - .labels(&["0%", "100%"]); - - let dataset_vector: Vec> = cpu_data - .iter() - .enumerate() - .rev() - .filter_map(|(itx, cpu)| { - if app_state.cpu_state.core_show_vec[itx] { - Some( - Dataset::default() - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style( - if app_state.app_config_fields.show_average_cpu && itx == 0 { + let use_dot = app_state.app_config_fields.use_dot; + let show_avg_cpu = app_state.app_config_fields.show_average_cpu; + let dataset_vector: Vec> = cpu_data + .iter() + .enumerate() + .rev() + .filter_map(|(itx, cpu)| { + if cpu_widget_state.core_show_vec[itx] { + Some( + Dataset::default() + .marker(if use_dot { + Marker::Dot + } else { + Marker::Braille + }) + .style(if show_avg_cpu && itx == 0 { self.colours.avg_colour_style } else { self.colours.cpu_colour_styles [itx % self.colours.cpu_colour_styles.len()] - }, - ) - .data(&cpu.cpu_data[..]), - ) - } else { - None - } - }) - .collect(); - - let title = if app_state.is_expanded && !app_state.cpu_state.is_showing_tray { - const TITLE_BASE: &str = " CPU ── Esc to go back "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, - ); - let result_title = - format!(" CPU ─{}─ Esc to go back ", "─".repeat(repeat_num as usize)); - - result_title - } else { - " CPU ".to_string() - }; - - let border_style = match app_state.current_widget_selected { - WidgetPosition::Cpu => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }; - - Chart::default() - .block( - Block::default() - .title(&title) - .title_style(if app_state.is_expanded { - border_style + }) + .data(&cpu.cpu_data[..]), + ) } else { - self.colours.widget_title_style - }) - .borders(Borders::ALL) - .border_style(border_style), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&dataset_vector) - .render(f, draw_loc); + None + } + }) + .collect(); + + let title = if app_state.is_expanded && !cpu_widget_state.is_showing_tray { + const TITLE_BASE: &str = " CPU ── Esc to go back "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = + format!(" CPU ─{}─ Esc to go back ", "─".repeat(repeat_num as usize)); + + result_title + } else { + " CPU ".to_string() + }; + + let border_style = if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }; + + Chart::default() + .block( + Block::default() + .title(&title) + .title_style(if app_state.is_expanded { + border_style + } else { + self.colours.widget_title_style + }) + .borders(Borders::ALL) + .border_style(border_style), + ) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&dataset_vector) + .render(f, draw_loc); + } } fn draw_cpu_legend( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { - let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; + if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1)) + { + cpu_widget_state.is_legend_hidden = false; + let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data; - let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; - let start_position = get_start_position( - num_rows, - &app_state.app_scroll_positions.scroll_direction, - &mut app_state - .app_scroll_positions - .cpu_scroll_state - .previous_scroll_position, - app_state - .app_scroll_positions - .cpu_scroll_state - .current_scroll_position, - app_state.is_resized, - ); + let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; + let start_position = get_start_position( + num_rows, + &cpu_widget_state.scroll_state.scroll_direction, + &mut cpu_widget_state.scroll_state.previous_scroll_position, + cpu_widget_state.scroll_state.current_scroll_position, + app_state.is_resized, + ); - let sliced_cpu_data = &cpu_data[start_position as usize..]; + let sliced_cpu_data = &cpu_data[start_position as usize..]; - let mut offset_scroll_index = (app_state - .app_scroll_positions - .cpu_scroll_state - .current_scroll_position - - start_position) as usize; - let cpu_rows = sliced_cpu_data.iter().enumerate().filter_map(|(itx, cpu)| { - let cpu_string_row: Vec> = if app_state.cpu_state.is_showing_tray { - vec![ - Cow::Borrowed(&cpu.cpu_name), - if app_state.cpu_state.core_show_vec[itx + start_position as usize] { - "[*]".into() - } else { - "[ ]".into() - }, - ] - } else if app_state.app_config_fields.show_disabled_data - || app_state.cpu_state.core_show_vec[itx] - { - vec![ - Cow::Borrowed(&cpu.cpu_name), - Cow::Borrowed(&cpu.legend_value), - ] - } else { - Vec::new() - }; + let mut offset_scroll_index = + (cpu_widget_state.scroll_state.current_scroll_position - start_position) as usize; + let show_disabled_data = app_state.app_config_fields.show_disabled_data; + let current_widget_id = app_state.current_widget.widget_id; + let show_avg_cpu = app_state.app_config_fields.show_average_cpu; - if cpu_string_row.is_empty() { - offset_scroll_index += 1; - None - } else { - Some(Row::StyledData( - cpu_string_row.into_iter(), - match app_state.current_widget_selected { - WidgetPosition::CpuLegend => { + let cpu_rows = sliced_cpu_data.iter().enumerate().filter_map(|(itx, cpu)| { + let cpu_string_row: Vec> = if cpu_widget_state.is_showing_tray { + vec![ + Cow::Borrowed(&cpu.cpu_name), + if cpu_widget_state.core_show_vec[itx + start_position as usize] { + "[*]".into() + } else { + "[ ]".into() + }, + ] + } else if show_disabled_data || cpu_widget_state.core_show_vec[itx] { + vec![ + Cow::Borrowed(&cpu.cpu_name), + Cow::Borrowed(&cpu.legend_value), + ] + } else { + Vec::new() + }; + + if cpu_string_row.is_empty() { + offset_scroll_index += 1; + None + } else { + Some(Row::StyledData( + cpu_string_row.into_iter(), + if current_widget_id == widget_id { if itx == offset_scroll_index { self.colours.currently_selected_text_style - } else if app_state.app_config_fields.show_average_cpu && itx == 0 { + } else if show_avg_cpu && itx == 0 { self.colours.avg_colour_style } else { self.colours.cpu_colour_styles[itx + start_position as usize % self.colours.cpu_colour_styles.len()] } - } - _ => { - if app_state.app_config_fields.show_average_cpu && itx == 0 { - self.colours.avg_colour_style - } else { - self.colours.cpu_colour_styles[itx - + start_position as usize - % self.colours.cpu_colour_styles.len()] - } - } - }, - )) - } - }); + } else if show_avg_cpu && itx == 0 { + self.colours.avg_colour_style + } else { + self.colours.cpu_colour_styles[itx + + start_position as usize % self.colours.cpu_colour_styles.len()] + }, + )) + } + }); - // Calculate widths - let width = f64::from(draw_loc.width); - let width_ratios = vec![0.5, 0.5]; + // Calculate widths + let width = f64::from(draw_loc.width); + let width_ratios = vec![0.5, 0.5]; - let variable_intrinsic_results = get_variable_intrinsic_widths( - width as u16, - &width_ratios, - if app_state.cpu_state.is_showing_tray { - &CPU_SELECT_LEGEND_HEADER_LENS - } else { - &CPU_LEGEND_HEADER_LENS - }, - ); - let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1]; - - let title = if app_state.cpu_state.is_showing_tray { - const TITLE_BASE: &str = " Esc to close "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + let variable_intrinsic_results = get_variable_intrinsic_widths( + width as u16, + &width_ratios, + if cpu_widget_state.is_showing_tray { + &CPU_SELECT_LEGEND_HEADER_LENS + } else { + &CPU_LEGEND_HEADER_LENS + }, ); - let result_title = format!("{} Esc to close ", "─".repeat(repeat_num as usize)); + let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1]; - result_title - } else { - "".to_string() - }; + let title = if cpu_widget_state.is_showing_tray { + const TITLE_BASE: &str = " Esc to close "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = format!("{} Esc to close ", "─".repeat(repeat_num as usize)); - let title_and_border_style = match app_state.current_widget_selected { - WidgetPosition::CpuLegend => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }; - - // Draw - Table::new( - if app_state.cpu_state.is_showing_tray { - CPU_SELECT_LEGEND_HEADER + result_title } else { - CPU_LEGEND_HEADER - } - .iter(), - cpu_rows, - ) - .block( - Block::default() - .title(&title) - .title_style(title_and_border_style) - .borders(Borders::ALL) - .border_style(title_and_border_style), - ) - .header_style(self.colours.table_header_style) - .widths( - &(intrinsic_widths - .iter() - .map(|calculated_width| Constraint::Length(*calculated_width as u16)) - .collect::>()), - ) - .render(f, draw_loc); + "".to_string() + }; + + let title_and_border_style = if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }; + + // Draw + Table::new( + if cpu_widget_state.is_showing_tray { + CPU_SELECT_LEGEND_HEADER + } else { + CPU_LEGEND_HEADER + } + .iter(), + cpu_rows, + ) + .block( + Block::default() + .title(&title) + .title_style(title_and_border_style) + .borders(Borders::ALL) + .border_style(title_and_border_style), + ) + .header_style(self.colours.table_header_style) + .widths( + &(intrinsic_widths + .iter() + .map(|calculated_width| Constraint::Length(*calculated_width as u16)) + .collect::>()), + ) + .render(f, draw_loc); + } } } diff --git a/src/canvas/widgets/disk_table.rs b/src/canvas/widgets/disk_table.rs index 2e4dbd2a..9b201bd9 100644 --- a/src/canvas/widgets/disk_table.rs +++ b/src/canvas/widgets/disk_table.rs @@ -8,7 +8,7 @@ use tui::{ }; use crate::{ - app::{self, WidgetPosition}, + app::{self}, canvas::{ drawing_utils::{get_start_position, get_variable_intrinsic_widths}, Painter, @@ -28,42 +28,38 @@ lazy_static! { pub trait DiskTableWidget { fn draw_disk_table( &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ); } impl DiskTableWidget for Painter { fn draw_disk_table( &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ) { - let disk_data: &[Vec] = &app_state.canvas_data.disk_data; - let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; - let start_position = get_start_position( - num_rows, - &app_state.app_scroll_positions.scroll_direction, - &mut app_state - .app_scroll_positions - .disk_scroll_state - .previous_scroll_position, - app_state - .app_scroll_positions - .disk_scroll_state - .current_scroll_position, - app_state.is_resized, - ); + if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) { + let disk_data: &mut [Vec] = &mut app_state.canvas_data.disk_data; + let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; + let start_position = get_start_position( + num_rows, + &disk_widget_state.scroll_state.scroll_direction, + &mut disk_widget_state.scroll_state.previous_scroll_position, + disk_widget_state.scroll_state.current_scroll_position, + app_state.is_resized, + ); - let sliced_vec = &disk_data[start_position as usize..]; - let mut disk_counter: i64 = 0; + let sliced_vec = &mut disk_data[start_position as usize..]; + let mut disk_counter: i64 = 0; - let disk_rows = sliced_vec.iter().map(|disk| { - Row::StyledData( - disk.iter(), - match app_state.current_widget_selected { - WidgetPosition::Disk => { + let current_widget_id = app_state.current_widget.widget_id; + let disk_rows = sliced_vec.iter().map(|disk| { + Row::StyledData( + disk.iter(), + if current_widget_id == widget_id + && disk_widget_state.scroll_state.current_scroll_position >= start_position + { if disk_counter as u64 - == app_state - .app_scroll_positions - .disk_scroll_state - .current_scroll_position + == disk_widget_state.scroll_state.current_scroll_position - start_position { disk_counter = -1; @@ -74,82 +70,84 @@ impl DiskTableWidget for Painter { } self.colours.text_style } - } - _ => self.colours.text_style, - }, - ) - }); + } else { + self.colours.text_style + }, + ) + }); - // Calculate widths - // TODO: [PRETTY] Ellipsis on strings? - let width = f64::from(draw_loc.width); - let width_ratios = [0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13]; - let variable_intrinsic_results = - get_variable_intrinsic_widths(width as u16, &width_ratios, &DISK_HEADERS_LENS); - let intrinsic_widths = &variable_intrinsic_results.0[0..variable_intrinsic_results.1]; + // Calculate widths + // TODO: [PRETTY] Ellipsis on strings? + let width = f64::from(draw_loc.width); + let width_ratios = [0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13]; + let variable_intrinsic_results = + get_variable_intrinsic_widths(width as u16, &width_ratios, &DISK_HEADERS_LENS); + let intrinsic_widths = &variable_intrinsic_results.0[0..variable_intrinsic_results.1]; - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Disk ── Esc to go back "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, - ); - let result_title = format!( - " Disk ─{}─ Esc to go back ", - "─".repeat(repeat_num as usize) - ); - result_title - } else if app_state.app_config_fields.use_basic_mode { - String::new() - } else { - " Disk ".to_string() - }; + let title = if app_state.is_expanded { + const TITLE_BASE: &str = " Disk ── Esc to go back "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = format!( + " Disk ─{}─ Esc to go back ", + "─".repeat(repeat_num as usize) + ); + result_title + } else if app_state.app_config_fields.use_basic_mode { + String::new() + } else { + " Disk ".to_string() + }; - let disk_block = if draw_border { - Block::default() - .title(&title) - .title_style(if app_state.is_expanded { - match app_state.current_widget_selected { - WidgetPosition::Disk => self.colours.highlighted_border_style, - _ => self.colours.border_style, - } - } else { - self.colours.widget_title_style - }) - .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Disk => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }) - } else { - match app_state.current_widget_selected { - WidgetPosition::Disk => Block::default() + let border_and_title_style = if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }; + + let disk_block = if draw_border { + Block::default() + .title(&title) + .title_style(if app_state.is_expanded { + border_and_title_style + } else { + self.colours.widget_title_style + }) + .borders(Borders::ALL) + .border_style(border_and_title_style) + } else if app_state.current_widget.widget_id == widget_id { + Block::default() .borders(*SIDE_BORDERS) - .border_style(self.colours.highlighted_border_style), - _ => Block::default().borders(Borders::NONE), - } - }; + .border_style(self.colours.highlighted_border_style) + } else { + Block::default().borders(Borders::NONE) + }; - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)].as_ref()) - .horizontal_margin(match app_state.current_widget_selected { - WidgetPosition::Disk => 0, - _ if !draw_border => 1, - _ => 0, - }) - .direction(Direction::Horizontal) - .split(draw_loc); + let margined_draw_loc = Layout::default() + .constraints([Constraint::Percentage(100)].as_ref()) + .horizontal_margin( + if app_state.current_widget.widget_id == widget_id || draw_border { + 0 + } else { + 1 + }, + ) + .direction(Direction::Horizontal) + .split(draw_loc); - // Draw! - Table::new(DISK_HEADERS.iter(), disk_rows) - .block(disk_block) - .header_style(self.colours.table_header_style) - .widths( - &(intrinsic_widths - .iter() - .map(|calculated_width| Constraint::Length(*calculated_width as u16)) - .collect::>()), - ) - .render(f, margined_draw_loc[0]); + // Draw! + Table::new(DISK_HEADERS.iter(), disk_rows) + .block(disk_block) + .header_style(self.colours.table_header_style) + .widths( + &(intrinsic_widths + .iter() + .map(|calculated_width| Constraint::Length(*calculated_width as u16)) + .collect::>()), + ) + .render(f, margined_draw_loc[0]); + } } } diff --git a/src/canvas/widgets/mem_basic.rs b/src/canvas/widgets/mem_basic.rs index 0dcfaced..75ffcacf 100644 --- a/src/canvas/widgets/mem_basic.rs +++ b/src/canvas/widgets/mem_basic.rs @@ -1,7 +1,7 @@ use std::cmp::max; use crate::{ - app::{App, WidgetPosition}, + app::App, canvas::{drawing_utils::*, Painter}, constants::*, }; @@ -15,13 +15,13 @@ use tui::{ pub trait MemBasicWidget { fn draw_basic_memory( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ); } impl MemBasicWidget for Painter { fn draw_basic_memory( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data; let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; @@ -31,7 +31,7 @@ impl MemBasicWidget for Painter { .horizontal_margin(1) .split(draw_loc); - if let WidgetPosition::BasicMem = app_state.current_widget_selected { + if app_state.current_widget.widget_id == widget_id { Block::default() .borders(*SIDE_BORDERS) .border_style(self.colours.highlighted_border_style) diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index 61b7ff72..db9eee0d 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -1,10 +1,6 @@ use std::cmp::max; -use crate::{ - app::{App, WidgetPosition}, - canvas::Painter, - constants::*, -}; +use crate::{app::App, canvas::Painter, constants::*}; use tui::{ backend::Backend, @@ -15,109 +11,112 @@ use tui::{ pub trait MemGraphWidget { fn draw_memory_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ); } impl MemGraphWidget for Painter { fn draw_memory_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { - let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data; - let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; + if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) { + let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data; + let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; - let display_time_labels = [ - format!("{}s", app_state.mem_state.current_display_time / 1000), - "0s".to_string(), - ]; - let x_axis = if app_state.app_config_fields.hide_time - || (app_state.app_config_fields.autohide_time - && app_state.mem_state.autohide_timer.is_none()) - { - Axis::default().bounds([0.0, app_state.mem_state.current_display_time as f64]) - } else if let Some(time) = app_state.mem_state.autohide_timer { - if std::time::Instant::now().duration_since(time).as_millis() - < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + let display_time_labels = [ + format!("{}s", mem_widget_state.current_display_time / 1000), + "0s".to_string(), + ]; + let x_axis = if app_state.app_config_fields.hide_time + || (app_state.app_config_fields.autohide_time + && mem_widget_state.autohide_timer.is_none()) { + Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0]) + } else if let Some(time) = mem_widget_state.autohide_timer { + if std::time::Instant::now().duration_since(time).as_millis() + < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + { + Axis::default() + .bounds([-(mem_widget_state.current_display_time as f64), 0.0]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + } else { + mem_widget_state.autohide_timer = None; + Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0]) + } + } else { Axis::default() - .bounds([0.0, app_state.mem_state.current_display_time as f64]) + .bounds([-(mem_widget_state.current_display_time as f64), 0.0]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) - } else { - app_state.mem_state.autohide_timer = None; - Axis::default().bounds([0.0, app_state.mem_state.current_display_time as f64]) - } - } else { - Axis::default() - .bounds([0.0, app_state.mem_state.current_display_time as f64]) + }; + + // Offset as the zero value isn't drawn otherwise... + let y_axis: Axis<'_, &str> = Axis::default() .style(self.colours.graph_style) .labels_style(self.colours.graph_style) - .labels(&display_time_labels) - }; + .bounds([-0.5, 100.5]) + .labels(&["0%", "100%"]); - // Offset as the zero value isn't drawn otherwise... - let y_axis: Axis<'_, &str> = Axis::default() - .style(self.colours.graph_style) - .labels_style(self.colours.graph_style) - .bounds([-0.5, 100.5]) - .labels(&["0%", "100%"]); - - let mem_canvas_vec: Vec> = vec![ - Dataset::default() - .name(&app_state.canvas_data.mem_label) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.ram_style) - .data(&mem_data), - Dataset::default() - .name(&app_state.canvas_data.swap_label) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.swap_style) - .data(&swap_data), - ]; - - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Memory ── Esc to go back "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, - ); - let result_title = format!( - " Memory ─{}─ Esc to go back ", - "─".repeat(repeat_num as usize) - ); - - result_title - } else { - " Memory ".to_string() - }; - - Chart::default() - .block( - Block::default() - .title(&title) - .title_style(if app_state.is_expanded { - self.colours.highlighted_border_style + let mem_canvas_vec: Vec> = vec![ + Dataset::default() + .name(&app_state.canvas_data.mem_label) + .marker(if app_state.app_config_fields.use_dot { + Marker::Dot } else { - self.colours.widget_title_style + Marker::Braille }) - .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Mem => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&mem_canvas_vec) - .render(f, draw_loc); + .style(self.colours.ram_style) + .data(&mem_data), + Dataset::default() + .name(&app_state.canvas_data.swap_label) + .marker(if app_state.app_config_fields.use_dot { + Marker::Dot + } else { + Marker::Braille + }) + .style(self.colours.swap_style) + .data(&swap_data), + ]; + + let title = if app_state.is_expanded { + const TITLE_BASE: &str = " Memory ── Esc to go back "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = format!( + " Memory ─{}─ Esc to go back ", + "─".repeat(repeat_num as usize) + ); + + result_title + } else { + " Memory ".to_string() + }; + + Chart::default() + .block( + Block::default() + .title(&title) + .title_style(if app_state.is_expanded { + self.colours.highlighted_border_style + } else { + self.colours.widget_title_style + }) + .borders(Borders::ALL) + .border_style(if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }), + ) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&mem_canvas_vec) + .render(f, draw_loc); + } } } diff --git a/src/canvas/widgets/network_basic.rs b/src/canvas/widgets/network_basic.rs index 58daac9e..59432890 100644 --- a/src/canvas/widgets/network_basic.rs +++ b/src/canvas/widgets/network_basic.rs @@ -1,8 +1,4 @@ -use crate::{ - app::{App, WidgetPosition}, - canvas::Painter, - constants::*, -}; +use crate::{app::App, canvas::Painter, constants::*}; use tui::{ backend::Backend, @@ -13,13 +9,13 @@ use tui::{ pub trait NetworkBasicWidget { fn draw_basic_network( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ); } impl NetworkBasicWidget for Painter { fn draw_basic_network( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { let divided_loc = Layout::default() .direction(Direction::Horizontal) @@ -38,7 +34,7 @@ impl NetworkBasicWidget for Painter { .horizontal_margin(1) .split(divided_loc[1]); - if let WidgetPosition::BasicNet = app_state.current_widget_selected { + if app_state.current_widget.widget_id == widget_id { Block::default() .borders(*SIDE_BORDERS) .border_style(self.colours.highlighted_border_style) diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index 206c9cd4..b21e886e 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -2,14 +2,14 @@ use lazy_static::lazy_static; use std::cmp::max; use crate::{ - app::{App, WidgetPosition}, + app::App, canvas::{drawing_utils::get_variable_intrinsic_widths, Painter}, constants::*, }; use tui::{ backend::Backend, - layout::{Constraint, Rect}, + layout::{Constraint, Direction, Layout, Rect}, terminal::Frame, widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, }; @@ -24,129 +24,156 @@ lazy_static! { } pub trait NetworkGraphWidget { + fn draw_network( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + ); + fn draw_network_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ); fn draw_network_labels( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ); } impl NetworkGraphWidget for Painter { - fn draw_network_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + fn draw_network( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { - let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx; - let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx; + let network_chunk = Layout::default() + .direction(Direction::Vertical) + .margin(0) + .constraints( + [ + Constraint::Length(max(draw_loc.height as i64 - 5, 0) as u16), + Constraint::Length(5), + ] + .as_ref(), + ) + .split(draw_loc); - let display_time_labels = [ - format!("{}s", app_state.net_state.current_display_time / 1000), - "0s".to_string(), - ]; - let x_axis = if app_state.app_config_fields.hide_time - || (app_state.app_config_fields.autohide_time - && app_state.net_state.autohide_timer.is_none()) - { - Axis::default().bounds([0.0, app_state.net_state.current_display_time as f64]) - } else if let Some(time) = app_state.net_state.autohide_timer { - if std::time::Instant::now().duration_since(time).as_millis() - < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + self.draw_network_graph(f, app_state, network_chunk[0], widget_id); + self.draw_network_labels(f, app_state, network_chunk[1], widget_id); + } + + fn draw_network_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + ) { + if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) { + let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx; + let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx; + + let display_time_labels = [ + format!("{}s", network_widget_state.current_display_time / 1000), + "0s".to_string(), + ]; + let x_axis = if app_state.app_config_fields.hide_time + || (app_state.app_config_fields.autohide_time + && network_widget_state.autohide_timer.is_none()) { + Axis::default().bounds([-(network_widget_state.current_display_time as f64), 0.0]) + } else if let Some(time) = network_widget_state.autohide_timer { + if std::time::Instant::now().duration_since(time).as_millis() + < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + { + Axis::default() + .bounds([-(network_widget_state.current_display_time as f64), 0.0]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + } else { + network_widget_state.autohide_timer = None; + Axis::default() + .bounds([-(network_widget_state.current_display_time as f64), 0.0]) + } + } else { Axis::default() - .bounds([0.0, app_state.net_state.current_display_time as f64]) + .bounds([-(network_widget_state.current_display_time as f64), 0.0]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) - } else { - app_state.net_state.autohide_timer = None; - Axis::default().bounds([0.0, app_state.net_state.current_display_time as f64]) - } - } else { - Axis::default() - .bounds([0.0, app_state.net_state.current_display_time as f64]) + }; + + // 0 is offset. + let y_axis: Axis<'_, &str> = Axis::default() .style(self.colours.graph_style) .labels_style(self.colours.graph_style) - .labels(&display_time_labels) - }; + .bounds([-0.5, 30_f64]) + .labels(&["0B", "1KiB", "1MiB", "1GiB"]); - // 0 is offset. - let y_axis: Axis<'_, &str> = Axis::default() - .style(self.colours.graph_style) - .labels_style(self.colours.graph_style) - .bounds([-0.5, 30_f64]) - .labels(&["0B", "1KiB", "1MiB", "1GiB"]); + let title = if app_state.is_expanded { + const TITLE_BASE: &str = " Network ── Esc to go back "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = format!( + " Network ─{}─ Esc to go back ", + "─".repeat(repeat_num as usize) + ); - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Network ── Esc to go back "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, - ); - let result_title = format!( - " Network ─{}─ Esc to go back ", - "─".repeat(repeat_num as usize) - ); + result_title + } else { + " Network ".to_string() + }; - result_title - } else { - " Network ".to_string() - }; - - Chart::default() - .block( - Block::default() - .title(&title) - .title_style(if app_state.is_expanded { - self.colours.highlighted_border_style - } else { - self.colours.widget_title_style - }) - .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Network => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&[ - Dataset::default() - .name(&format!("RX: {:7}", app_state.canvas_data.rx_display)) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.rx_style) - .data(&network_data_rx), - Dataset::default() - .name(&format!("TX: {:7}", app_state.canvas_data.tx_display)) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.tx_style) - .data(&network_data_tx), - Dataset::default() - .name(&format!( - "Total RX: {:7}", - app_state.canvas_data.total_rx_display - )) - .style(self.colours.total_rx_style), - Dataset::default() - .name(&format!( - "Total TX: {:7}", - app_state.canvas_data.total_tx_display - )) - .style(self.colours.total_tx_style), - ]) - .render(f, draw_loc); + Chart::default() + .block( + Block::default() + .title(&title) + .title_style(if app_state.is_expanded { + self.colours.highlighted_border_style + } else { + self.colours.widget_title_style + }) + .borders(Borders::ALL) + .border_style(if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }), + ) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&[ + Dataset::default() + .name(&format!("RX: {:7}", app_state.canvas_data.rx_display)) + .marker(if app_state.app_config_fields.use_dot { + Marker::Dot + } else { + Marker::Braille + }) + .style(self.colours.rx_style) + .data(&network_data_rx), + Dataset::default() + .name(&format!("TX: {:7}", app_state.canvas_data.tx_display)) + .marker(if app_state.app_config_fields.use_dot { + Marker::Dot + } else { + Marker::Braille + }) + .style(self.colours.tx_style) + .data(&network_data_tx), + Dataset::default() + .name(&format!( + "Total RX: {:7}", + app_state.canvas_data.total_rx_display + )) + .style(self.colours.total_rx_style), + Dataset::default() + .name(&format!( + "Total TX: {:7}", + app_state.canvas_data.total_tx_display + )) + .style(self.colours.total_tx_style), + ]) + .render(f, draw_loc); + } } fn draw_network_labels( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { let rx_display = &app_state.canvas_data.rx_display; let tx_display = &app_state.canvas_data.tx_display; @@ -176,9 +203,10 @@ impl NetworkGraphWidget for Painter { // Draw Table::new(NETWORK_HEADERS.iter(), mapped_network) .block(Block::default().borders(Borders::ALL).border_style( - match app_state.current_widget_selected { - WidgetPosition::Network => self.colours.highlighted_border_style, - _ => self.colours.border_style, + if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style }, )) .header_style(self.colours.table_header_style) diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index fd8c6b6f..0a053720 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -1,7 +1,7 @@ use std::cmp::{max, min}; use crate::{ - app::{self, App, WidgetPosition}, + app::{self, App}, canvas::{ drawing_utils::{ get_search_start_position, get_start_position, get_variable_intrinsic_widths, @@ -9,7 +9,6 @@ use crate::{ Painter, }, constants::*, - data_conversion::ConvertedProcessData, }; use tui::{ @@ -25,260 +24,287 @@ use unicode_width::UnicodeWidthStr; pub trait ProcessTableWidget { fn draw_process_and_search( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ); fn draw_processes_table( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ); fn draw_search_field( &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( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ) { - let search_width = if draw_border { 5 } else { 3 }; + if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) { + let search_width = if draw_border { 5 } else { 3 }; + if process_widget_state.is_search_enabled() { + let processes_chunk = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(0), Constraint::Length(search_width)].as_ref()) + .split(draw_loc); - if app_state.is_searching() { - let processes_chunk = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(0), Constraint::Length(search_width)].as_ref()) - .split(draw_loc); - - self.draw_processes_table(f, app_state, processes_chunk[0], draw_border); - self.draw_search_field(f, app_state, processes_chunk[1], draw_border); - } else { - self.draw_processes_table(f, app_state, draw_loc, draw_border); + self.draw_processes_table(f, app_state, processes_chunk[0], draw_border, widget_id); + self.draw_search_field( + f, + app_state, + processes_chunk[1], + draw_border, + widget_id + 1, + ); + } else { + self.draw_processes_table(f, app_state, draw_loc, draw_border, widget_id); + } } } fn draw_processes_table( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ) { - let process_data: &[ConvertedProcessData] = &app_state.canvas_data.finalized_process_data; + if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) { + if let Some(process_data) = &app_state + .canvas_data + .finalized_process_data_map + .get(&widget_id) + { + // Admittedly this is kinda a hack... but we need to: + // * Scroll + // * Show/hide elements based on scroll position + // + // As such, we use a process_counter to know when we've + // hit the process we've currently scrolled to. + // We also need to move the list - we can + // do so by hiding some elements! + let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; + let is_on_widget = widget_id == app_state.current_widget.widget_id; - // Admittedly this is kinda a hack... but we need to: - // * Scroll - // * Show/hide elements based on scroll position - // - // As such, we use a process_counter to know when we've - // hit the process we've currently scrolled to. - // We also need to move the list - we can - // do so by hiding some elements! - let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; + let position = get_start_position( + num_rows, + &proc_widget_state.scroll_state.scroll_direction, + &mut proc_widget_state.scroll_state.previous_scroll_position, + proc_widget_state.scroll_state.current_scroll_position, + app_state.is_resized, + ); - let position = get_start_position( - num_rows, - &app_state.app_scroll_positions.scroll_direction, - &mut app_state - .app_scroll_positions - .process_scroll_state - .previous_scroll_position, - app_state - .app_scroll_positions - .process_scroll_state - .current_scroll_position, - app_state.is_resized, - ); - - // Sanity check - let start_position = if position >= process_data.len() as u64 { - std::cmp::max(0, process_data.len() as i64 - 1) as u64 - } else { - position - }; - - let sliced_vec = &process_data[start_position as usize..]; - let mut process_counter: i64 = 0; - - // Draw! - let process_rows = sliced_vec.iter().map(|process| { - let stringified_process_vec: Vec = vec![ - if app_state.is_grouped() { - process.group_pids.len().to_string() + // Sanity check + let start_position = if position >= process_data.len() as u64 { + std::cmp::max(0, process_data.len() as i64 - 1) as u64 } else { - process.pid.to_string() - }, - process.name.clone(), - format!("{:.1}%", process.cpu_usage), - format!("{:.1}%", process.mem_usage), - ]; - Row::StyledData( - stringified_process_vec.into_iter(), - match app_state.current_widget_selected { - WidgetPosition::Process => { - if process_counter as u64 - == app_state - .app_scroll_positions - .process_scroll_state - .current_scroll_position - - start_position - { - process_counter = -1; - self.colours.currently_selected_text_style + position + }; + + let sliced_vec = &process_data[start_position as usize..]; + let mut process_counter: i64 = 0; + + // Draw! + let process_rows = sliced_vec.iter().map(|process| { + let stringified_process_vec: Vec = vec![ + if proc_widget_state.is_grouped { + process.group_pids.len().to_string() } else { - if process_counter >= 0 { - process_counter += 1; + process.pid.to_string() + }, + process.name.clone(), + format!("{:.1}%", process.cpu_usage), + format!("{:.1}%", process.mem_usage), + ]; + Row::StyledData( + stringified_process_vec.into_iter(), + if is_on_widget { + if process_counter as u64 + == proc_widget_state.scroll_state.current_scroll_position + - start_position + { + process_counter = -1; + self.colours.currently_selected_text_style + } else { + if process_counter >= 0 { + process_counter += 1; + } + self.colours.text_style } + } else { self.colours.text_style - } - } - _ => self.colours.text_style, - }, - ) - }); + }, + ) + }); - use app::data_harvester::processes::ProcessSorting; - let mut pid_or_name = if app_state.is_grouped() { - "Count" - } else { - "PID(p)" - } - .to_string(); - let mut name = "Name(n)".to_string(); - let mut cpu = "CPU%(c)".to_string(); - let mut mem = "Mem%(m)".to_string(); + use app::data_harvester::processes::ProcessSorting; + let mut pid_or_name = if proc_widget_state.is_grouped { + "Count" + } else { + "PID(p)" + } + .to_string(); + let mut name = "Name(n)".to_string(); + let mut cpu = "CPU%(c)".to_string(); + let mut mem = "Mem%(m)".to_string(); - let direction_val = if app_state.process_sorting_reverse { - "â–¼".to_string() - } else { - "â–²".to_string() - }; + let direction_val = if proc_widget_state.process_sorting_reverse { + "â–¼".to_string() + } else { + "â–²".to_string() + }; - match app_state.process_sorting_type { - ProcessSorting::CPU => cpu += &direction_val, - ProcessSorting::MEM => mem += &direction_val, - ProcessSorting::PID => pid_or_name += &direction_val, - ProcessSorting::NAME => name += &direction_val, - }; + match proc_widget_state.process_sorting_type { + ProcessSorting::CPU => cpu += &direction_val, + ProcessSorting::MEM => mem += &direction_val, + ProcessSorting::PID => pid_or_name += &direction_val, + ProcessSorting::NAME => name += &direction_val, + }; - let process_headers = [pid_or_name, name, cpu, mem]; - let process_headers_lens: Vec = process_headers - .iter() - .map(|entry| entry.len()) - .collect::>(); + let process_headers = [pid_or_name, name, cpu, mem]; + let process_headers_lens: Vec = process_headers + .iter() + .map(|entry| entry.len()) + .collect::>(); - // Calculate widths - let width = f64::from(draw_loc.width); - let width_ratios = [0.2, 0.4, 0.2, 0.2]; - let variable_intrinsic_results = - get_variable_intrinsic_widths(width as u16, &width_ratios, &process_headers_lens); - let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1]; - - let title = if draw_border { - if app_state.is_expanded && !app_state.process_search_state.search_state.is_enabled { - const TITLE_BASE: &str = " Processes ── Esc to go back "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, - ); - let result_title = format!( - " Processes ─{}─ Esc to go back ", - "─".repeat(repeat_num as usize) + // Calculate widths + let width = f64::from(draw_loc.width); + let width_ratios = [0.2, 0.4, 0.2, 0.2]; + let variable_intrinsic_results = get_variable_intrinsic_widths( + width as u16, + &width_ratios, + &process_headers_lens, ); + let intrinsic_widths = + &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1]; - result_title - } else { - " Processes ".to_string() - } - } else { - String::default() - }; + let title = if draw_border { + if app_state.is_expanded + && !proc_widget_state + .process_search_state + .search_state + .is_enabled + { + const TITLE_BASE: &str = " Processes ── Esc to go back "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = format!( + " Processes ─{}─ Esc to go back ", + "─".repeat(repeat_num as usize) + ); - let process_block = if draw_border { - Block::default() - .title(&title) - .title_style(if app_state.is_expanded { - match app_state.current_widget_selected { - WidgetPosition::Process => self.colours.highlighted_border_style, - _ => self.colours.border_style, + result_title + } else { + " Processes ".to_string() } } else { - self.colours.widget_title_style - }) - .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Process => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }) - } else { - match app_state.current_widget_selected { - WidgetPosition::Process => Block::default() - .borders(*SIDE_BORDERS) - .border_style(self.colours.highlighted_border_style), - _ => Block::default().borders(Borders::NONE), + String::default() + }; + + let border_and_title_style = if is_on_widget { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }; + + let process_block = if draw_border { + Block::default() + .title(&title) + .title_style(if app_state.is_expanded { + border_and_title_style + } else { + self.colours.widget_title_style + }) + .borders(Borders::ALL) + .border_style(border_and_title_style) + } else if is_on_widget { + Block::default() + .borders(*SIDE_BORDERS) + .border_style(self.colours.highlighted_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); + + Table::new(process_headers.iter(), process_rows) + .block(process_block) + .header_style(self.colours.table_header_style) + .widths( + &(intrinsic_widths + .iter() + .map(|calculated_width| Constraint::Length(*calculated_width as u16)) + .collect::>()), + ) + .render(f, margined_draw_loc[0]); } - }; - - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)].as_ref()) - .horizontal_margin(match app_state.current_widget_selected { - WidgetPosition::Process => 0, - _ if !draw_border => 1, - _ => 0, - }) - .direction(Direction::Horizontal) - .split(draw_loc); - - Table::new(process_headers.iter(), process_rows) - .block(process_block) - .header_style(self.colours.table_header_style) - .widths( - &(intrinsic_widths - .iter() - .map(|calculated_width| Constraint::Length(*calculated_width as u16)) - .collect::>()), - ) - .render(f, margined_draw_loc[0]); + } } fn draw_search_field( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ) { - let pid_search_text = "Search by PID (Tab for Name): "; - let name_search_text = "Search by Name (Tab for PID): "; - let grouped_search_text = "Search by Name: "; - let num_columns = draw_loc.width as usize; + if let Some(proc_widget_state) = + app_state.proc_state.widget_states.get_mut(&(widget_id - 1)) + { + let pid_search_text = "Search by PID (Tab for Name): "; + let name_search_text = "Search by Name (Tab for PID): "; + let grouped_search_text = "Search by Name: "; + let num_columns = draw_loc.width as usize; - let chosen_text = if app_state.is_grouped() { - grouped_search_text - } else if app_state.process_search_state.is_searching_with_pid { - pid_search_text - } else { - name_search_text - }; + let is_on_widget = widget_id == app_state.current_widget.widget_id; - let search_title: &str = if chosen_text.len() == min(num_columns / 2, chosen_text.len()) { - chosen_text - } else if chosen_text.is_empty() { - "" - } else { - "> " - }; + let chosen_text = if proc_widget_state.is_grouped { + grouped_search_text + } else if proc_widget_state.process_search_state.is_searching_with_pid { + pid_search_text + } else { + name_search_text + }; - let num_chars_for_text = search_title.len(); + let small_mode = chosen_text.len() != min(num_columns / 2, chosen_text.len()); + let search_title: &str = if !small_mode { + chosen_text + } else if chosen_text.is_empty() { + "" + } else if proc_widget_state.process_search_state.is_searching_with_pid { + "p> " + } else { + "n> " + }; - let mut search_text = vec![Text::styled(search_title, self.colours.table_header_style)]; + let num_chars_for_text = search_title.len(); - let cursor_position = app_state.get_cursor_position(); - let current_cursor_position = app_state.get_char_cursor_position(); + let mut search_text = vec![Text::styled(search_title, self.colours.table_header_style)]; - let start_position: usize = get_search_start_position( - num_columns - num_chars_for_text - 5, - &app_state.process_search_state.search_state.cursor_direction, - &mut app_state.process_search_state.search_state.cursor_bar, - current_cursor_position, - app_state.is_resized, - ); + let cursor_position = proc_widget_state.get_cursor_position(); + let current_cursor_position = proc_widget_state.get_char_cursor_position(); - let query = app_state.get_current_search_query().as_str(); - let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true); - let mut current_grapheme_posn = 0; - let query_with_cursor: Vec> = - if let WidgetPosition::ProcessSearch = app_state.current_widget_selected { + let start_position: usize = get_search_start_position( + num_columns - num_chars_for_text - 5, + &proc_widget_state + .process_search_state + .search_state + .cursor_direction, + &mut proc_widget_state + .process_search_state + .search_state + .cursor_bar, + current_cursor_position, + app_state.is_resized, + ); + + let query = proc_widget_state.get_current_search_query().as_str(); + let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true); + let mut current_grapheme_posn = 0; + let query_with_cursor: Vec> = if is_on_widget { let mut res = grapheme_indices .filter_map(|grapheme| { current_grapheme_posn += UnicodeWidthStr::width(grapheme.1); @@ -320,124 +346,117 @@ impl ProcessTableWidget for Painter { .collect::>() }; - // Text options shamelessly stolen from VS Code. - let mut option_text = vec![]; - let case_style = if !app_state.process_search_state.is_ignoring_case { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - let whole_word_style = if app_state.process_search_state.is_searching_whole_word { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - let regex_style = if app_state.process_search_state.is_searching_with_regex { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - let case_text = format!( - "Match Case ({})[{}]", - if self.is_mac_os { "F1" } else { "Alt+C" }, - if !app_state.process_search_state.is_ignoring_case { - "*" + // Text options shamelessly stolen from VS Code. + let case_style = if !proc_widget_state.process_search_state.is_ignoring_case { + self.colours.currently_selected_text_style } else { - " " - } - ); + self.colours.text_style + }; - let whole_text = format!( - "Match Whole Word ({})[{}]", - if self.is_mac_os { "F2" } else { "Alt+W" }, - if app_state.process_search_state.is_searching_whole_word { - "*" + let whole_word_style = if proc_widget_state + .process_search_state + .is_searching_whole_word + { + self.colours.currently_selected_text_style } else { - " " - } - ); + self.colours.text_style + }; - let regex_text = format!( - "Use Regex ({})[{}]", - if self.is_mac_os { "F3" } else { "Alt+R" }, - if app_state.process_search_state.is_searching_with_regex { - "*" + let regex_style = if proc_widget_state + .process_search_state + .is_searching_with_regex + { + self.colours.currently_selected_text_style } else { - " " - } - ); + self.colours.text_style + }; - let option_row = vec![ - Text::raw("\n\n"), - Text::styled(&case_text, case_style), - Text::raw(" "), - Text::styled(&whole_text, whole_word_style), - Text::raw(" "), - Text::styled(®ex_text, regex_style), - ]; - option_text.extend(option_row); - - search_text.extend(query_with_cursor); - search_text.extend(option_text); - - let current_border_style = if app_state - .process_search_state - .search_state - .is_invalid_search - { - *INVALID_REGEX_STYLE - } else { - match app_state.current_widget_selected { - WidgetPosition::ProcessSearch => self.colours.highlighted_border_style, - _ => self.colours.border_style, - } - }; - - let title = if draw_border { - const TITLE_BASE: &str = " Esc to close "; - - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + let mut option_text = vec![]; + let case_text = format!( + "{}({})", + if small_mode { "Case" } else { "Match Case " }, + if self.is_mac_os { "F1" } else { "Alt+C" }, ); - format!("{} Esc to close ", "─".repeat(repeat_num as usize)) - } else { - String::new() - }; - let process_search_block = if draw_border { - Block::default() - .title(&title) - .title_style(current_border_style) - .borders(Borders::ALL) - .border_style(current_border_style) - } else { - match app_state.current_widget_selected { - WidgetPosition::ProcessSearch => Block::default() + let whole_text = format!( + "{}({})", + if small_mode { + "Whole" + } else { + "Match Whole Word " + }, + if self.is_mac_os { "F2" } else { "Alt+W" }, + ); + + let regex_text = format!( + "{}({})", + if small_mode { "Regex" } else { "Use Regex " }, + if self.is_mac_os { "F3" } else { "Alt+R" }, + ); + + let option_row = vec![ + Text::raw("\n\n"), + Text::styled(&case_text, case_style), + Text::raw(if small_mode { " " } else { " " }), + Text::styled(&whole_text, whole_word_style), + Text::raw(if small_mode { " " } else { " " }), + Text::styled(®ex_text, regex_style), + ]; + option_text.extend(option_row); + + search_text.extend(query_with_cursor); + search_text.extend(option_text); + + let current_border_style = if proc_widget_state + .process_search_state + .search_state + .is_invalid_search + { + *INVALID_REGEX_STYLE + } else if is_on_widget { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }; + + let title = if draw_border { + const TITLE_BASE: &str = " Esc to close "; + + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + format!("{} Esc to close ", "─".repeat(repeat_num as usize)) + } else { + String::new() + }; + + let process_search_block = if draw_border { + Block::default() + .title(&title) + .title_style(current_border_style) + .borders(Borders::ALL) + .border_style(current_border_style) + } else if is_on_widget { + Block::default() .borders(*SIDE_BORDERS) - .border_style(current_border_style), - _ => Block::default().borders(Borders::NONE), - } - }; + .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(match app_state.current_widget_selected { - WidgetPosition::ProcessSearch => 0, - _ if !draw_border => 1, - _ => 0, - }) - .direction(Direction::Horizontal) - .split(draw_loc); + 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); - Paragraph::new(search_text.iter()) - .block(process_search_block) - .style(self.colours.text_style) - .alignment(Alignment::Left) - .wrap(false) - .render(f, margined_draw_loc[0]); + Paragraph::new(search_text.iter()) + .block(process_search_block) + .style(self.colours.text_style) + .alignment(Alignment::Left) + .wrap(false) + .render(f, margined_draw_loc[0]); + } } } diff --git a/src/canvas/widgets/temp_table.rs b/src/canvas/widgets/temp_table.rs index 5ce3228f..f34c23c8 100644 --- a/src/canvas/widgets/temp_table.rs +++ b/src/canvas/widgets/temp_table.rs @@ -9,7 +9,7 @@ use tui::{ }; use crate::{ - app::{self, WidgetPosition}, + app, canvas::{ drawing_utils::{get_start_position, get_variable_intrinsic_widths}, Painter, @@ -28,43 +28,37 @@ lazy_static! { pub trait TempTableWidget { fn draw_temp_table( &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ); } impl TempTableWidget for Painter { fn draw_temp_table( &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, + widget_id: u64, ) { - let temp_sensor_data: &[Vec] = &app_state.canvas_data.temp_sensor_data; + if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) { + let temp_sensor_data: &mut [Vec] = &mut app_state.canvas_data.temp_sensor_data; - let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; - let start_position = get_start_position( - num_rows, - &app_state.app_scroll_positions.scroll_direction, - &mut app_state - .app_scroll_positions - .temp_scroll_state - .previous_scroll_position, - app_state - .app_scroll_positions - .temp_scroll_state - .current_scroll_position, - app_state.is_resized, - ); + let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64; + let start_position = get_start_position( + num_rows, + &temp_widget_state.scroll_state.scroll_direction, + &mut temp_widget_state.scroll_state.previous_scroll_position, + temp_widget_state.scroll_state.current_scroll_position, + app_state.is_resized, + ); - let sliced_vec = &temp_sensor_data[start_position as usize..]; - let mut temp_row_counter: i64 = 0; + let sliced_vec = &temp_sensor_data[start_position as usize..]; + let mut temp_row_counter: i64 = 0; + let current_widget_id = app_state.current_widget.widget_id; - let temperature_rows = sliced_vec.iter().map(|temp_row| { - Row::StyledData( - temp_row.iter(), - match app_state.current_widget_selected { - WidgetPosition::Temp => { + let temperature_rows = sliced_vec.iter().map(|temp_row| { + Row::StyledData( + temp_row.iter(), + if current_widget_id == widget_id { if temp_row_counter as u64 - == app_state - .app_scroll_positions - .temp_scroll_state - .current_scroll_position + == temp_widget_state.scroll_state.current_scroll_position - start_position { temp_row_counter = -1; @@ -75,82 +69,86 @@ impl TempTableWidget for Painter { } self.colours.text_style } - } - _ => self.colours.text_style, - }, - ) - }); + } else { + self.colours.text_style + }, + ) + }); - // Calculate widths - let width = f64::from(draw_loc.width); - let width_ratios = [0.5, 0.5]; - let variable_intrinsic_results = - get_variable_intrinsic_widths(width as u16, &width_ratios, &TEMP_HEADERS_LENS); - let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1]; + // Calculate widths + let width = f64::from(draw_loc.width); + let width_ratios = [0.5, 0.5]; + let variable_intrinsic_results = + get_variable_intrinsic_widths(width as u16, &width_ratios, &TEMP_HEADERS_LENS); + let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1]; - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Temperatures ── Esc to go back "; - let repeat_num = max( - 0, - draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, - ); - let result_title = format!( - " Temperatures ─{}─ Esc to go back ", - "─".repeat(repeat_num as usize) - ); + let title = if app_state.is_expanded { + const TITLE_BASE: &str = " Temperatures ── Esc to go back "; + let repeat_num = max( + 0, + draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2, + ); + let result_title = format!( + " Temperatures ─{}─ Esc to go back ", + "─".repeat(repeat_num as usize) + ); - result_title - } else if app_state.app_config_fields.use_basic_mode { - String::new() - } else { - " Temperatures ".to_string() - }; + result_title + } else if app_state.app_config_fields.use_basic_mode { + String::new() + } else { + " Temperatures ".to_string() + }; - let temp_block = if draw_border { - Block::default() - .title(&title) - .title_style(if app_state.is_expanded { - match app_state.current_widget_selected { - WidgetPosition::Temp => self.colours.highlighted_border_style, - _ => self.colours.border_style, - } - } else { - self.colours.widget_title_style - }) - .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Temp => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }) - } else { - match app_state.current_widget_selected { - WidgetPosition::Temp => Block::default() + let temp_block = if draw_border { + Block::default() + .title(&title) + .title_style(if app_state.is_expanded { + if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + } + } else { + self.colours.widget_title_style + }) + .borders(Borders::ALL) + .border_style(if app_state.current_widget.widget_id == widget_id { + self.colours.highlighted_border_style + } else { + self.colours.border_style + }) + } else if app_state.current_widget.widget_id == widget_id { + Block::default() .borders(*SIDE_BORDERS) - .border_style(self.colours.highlighted_border_style), - _ => Block::default().borders(Borders::NONE), - } - }; + .border_style(self.colours.highlighted_border_style) + } else { + Block::default().borders(Borders::NONE) + }; - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)].as_ref()) - .horizontal_margin(match app_state.current_widget_selected { - WidgetPosition::Temp => 0, - _ if !draw_border => 1, - _ => 0, - }) - .direction(Direction::Horizontal) - .split(draw_loc); + let margined_draw_loc = Layout::default() + .constraints([Constraint::Percentage(100)].as_ref()) + .horizontal_margin( + if app_state.current_widget.widget_id == widget_id || draw_border { + 0 + } else { + 1 + }, + ) + .direction(Direction::Horizontal) + .split(draw_loc); - // Draw - Table::new(TEMP_HEADERS.iter(), temperature_rows) - .block(temp_block) - .header_style(self.colours.table_header_style) - .widths( - &(intrinsic_widths - .iter() - .map(|calculated_width| Constraint::Length(*calculated_width as u16)) - .collect::>()), - ) - .render(f, margined_draw_loc[0]); + // Draw + Table::new(TEMP_HEADERS.iter(), temperature_rows) + .block(temp_block) + .header_style(self.colours.table_header_style) + .widths( + &(intrinsic_widths + .iter() + .map(|calculated_width| Constraint::Length(*calculated_width as u16)) + .collect::>()), + ) + .render(f, margined_draw_loc[0]); + } } } diff --git a/src/constants.rs b/src/constants.rs index e4c274b4..b039a306 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,8 @@ use lazy_static::lazy_static; +// Default widget ID +pub const DEFAULT_WIDGET_ID: u64 = 56709; + // How long to store data. pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data. @@ -56,7 +59,7 @@ pub const GENERAL_HELP_TEXT: [&str; 18] = [ pub const PROCESS_HELP_TEXT: [&str; 8] = [ "Process Keybindings\n\n", - "dd Kill the highlighted process\n", + "dd, Delete Kill the highlighted process\n", "c Sort by CPU usage\n", "m Sort by memory usage\n", "p Sort by PID\n", @@ -134,20 +137,16 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" #temperature_type = "fahrenheit" #temperature_type = "celsius" -# Defaults to processes. Default widget is one of: -#default_widget = "cpu_default" -#default_widget = "memory_default" -#default_widget = "disk_default" -#default_widget = "temperature_default" -#default_widget = "network_default" -#default_widget = "process_default" - # The default time interval (in milliseconds). #default_time_value = 60000 # The time delta on each zoom in/out action (in milliseconds). #time_delta = 15000 +# Override layout default widget +#default_widget_type = "proc" +#default_widget_count = 1 + # These are all the components that support custom theming. Currently, it only # supports taking in a string representing a hex colour. Note that colour support # will, at the end of the day, depend on terminal support - for example, the @@ -200,4 +199,28 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" # Represents the cursor's colour. #cursor_color="#458588" + +# The default widget layout: +#[[row]] +# ratio=30 +# [[row.child]] +# type="cpu" +#[[row]] +# ratio=40 +# [[row.child]] +# ratio=4 +# type="mem" +# [[row.child]] +# ratio=3 +# [[row.child.child]] +# type="temp" +# [[row.child.child]] +# type="disk" +#[[row]] +# ratio=30 +# [[row.child]] +# type="net" +# [[row.child]] +# type="proc" +# default=true "##; diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 93473a2a..8feca30a 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -103,7 +103,7 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec Vec { let mut cpu_data_vector: Vec = Vec::new(); let current_time = if is_frozen { @@ -117,8 +117,7 @@ pub fn convert_cpu_data_points( }; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = - (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); + let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); for (itx, cpu) in data.cpu_data.iter().enumerate() { // Check if the vector exists yet @@ -133,15 +132,15 @@ pub fn convert_cpu_data_points( //Insert joiner points for &(joiner_offset, joiner_val) in &cpu.1 { - let offset_time = time_from_start - joiner_offset as f64; + let offset_time = time_from_start + joiner_offset as f64; cpu_data_vector[itx_offset] .cpu_data - .push((offset_time, joiner_val)); + .push((-offset_time, joiner_val)); } cpu_data_vector[itx_offset] .cpu_data - .push((time_from_start, cpu.0)); + .push((-time_from_start, cpu.0)); } if *time == current_time { @@ -153,7 +152,7 @@ pub fn convert_cpu_data_points( } pub fn convert_mem_data_points( - current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, + current_data: &data_farmer::DataCollection, is_frozen: bool, ) -> Vec { let mut result: Vec = Vec::new(); let current_time = if is_frozen { @@ -167,16 +166,15 @@ pub fn convert_mem_data_points( }; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = - (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); + let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); //Insert joiner points for &(joiner_offset, joiner_val) in &data.mem_data.1 { - let offset_time = time_from_start - joiner_offset as f64; - result.push((offset_time, joiner_val)); + let offset_time = time_from_start + joiner_offset as f64; + result.push((-offset_time, joiner_val)); } - result.push((time_from_start, data.mem_data.0)); + result.push((-time_from_start, data.mem_data.0)); if *time == current_time { break; @@ -187,7 +185,7 @@ pub fn convert_mem_data_points( } pub fn convert_swap_data_points( - current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, + current_data: &data_farmer::DataCollection, is_frozen: bool, ) -> Vec { let mut result: Vec = Vec::new(); let current_time = if is_frozen { @@ -201,16 +199,15 @@ pub fn convert_swap_data_points( }; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = - (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); + let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); //Insert joiner points for &(joiner_offset, joiner_val) in &data.swap_data.1 { - let offset_time = time_from_start - joiner_offset as f64; - result.push((offset_time, joiner_val)); + let offset_time = time_from_start + joiner_offset as f64; + result.push((-offset_time, joiner_val)); } - result.push((time_from_start, data.swap_data.0)); + result.push((-time_from_start, data.swap_data.0)); if *time == current_time { break; @@ -257,7 +254,7 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String } pub fn get_rx_tx_data_points( - current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, + current_data: &data_farmer::DataCollection, is_frozen: bool, ) -> (Vec, Vec) { let mut rx: Vec = Vec::new(); let mut tx: Vec = Vec::new(); @@ -274,22 +271,21 @@ pub fn get_rx_tx_data_points( // TODO: [REFACTOR] Can we use collect on this, CPU, and MEM? for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = - (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); + let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); //Insert joiner points for &(joiner_offset, joiner_val) in &data.rx_data.1 { - let offset_time = time_from_start - joiner_offset as f64; - rx.push((offset_time, joiner_val)); + let offset_time = time_from_start + joiner_offset as f64; + rx.push((-offset_time, joiner_val)); } for &(joiner_offset, joiner_val) in &data.tx_data.1 { - let offset_time = time_from_start - joiner_offset as f64; - tx.push((offset_time, joiner_val)); + let offset_time = time_from_start + joiner_offset as f64; + tx.push((-offset_time, joiner_val)); } - rx.push((time_from_start, data.rx_data.0)); - tx.push((time_from_start, data.tx_data.0)); + rx.push((-time_from_start, data.rx_data.0)); + tx.push((-time_from_start, data.tx_data.0)); if *time == current_time { break; @@ -300,9 +296,9 @@ pub fn get_rx_tx_data_points( } pub fn convert_network_data_points( - current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, + current_data: &data_farmer::DataCollection, is_frozen: bool, ) -> ConvertedNetworkData { - let (rx, tx) = get_rx_tx_data_points(current_data, display_time, is_frozen); + let (rx, tx) = get_rx_tx_data_points(current_data, is_frozen); let total_rx_converted_result: (f64, String); let rx_converted_result: (f64, String); diff --git a/src/main.rs b/src/main.rs index 972237bf..5ffe5c86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,14 +86,8 @@ fn get_matches() -> clap::ArgMatches<'static> { (@arg TIME_DELTA: -d --time_delta +takes_value "The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s.") (@arg HIDE_TIME: --hide_time "Completely hide the time scaling") (@arg AUTOHIDE_TIME: --autohide_time "Automatically hide the time scaling in graphs after being shown for a brief moment when zoomed in/out. If time is disabled via --hide_time then this will have no effect.") - (@group DEFAULT_WIDGET => - (@arg CPU_WIDGET: --cpu_default "Selects the CPU widget to be selected by default.") - (@arg MEM_WIDGET: --memory_default "Selects the memory widget to be selected by default.") - (@arg DISK_WIDGET: --disk_default "Selects the disk widget to be selected by default.") - (@arg TEMP_WIDGET: --temperature_default "Selects the temp widget to be selected by default.") - (@arg NET_WIDGET: --network_default "Selects the network widget to be selected by default.") - (@arg PROC_WIDGET: --process_default "Selects the process widget to be selected by default. This is the default if nothing is set.") - ) + (@arg DEFAULT_WIDGET_TYPE: --default_widget_type +takes_value "The default widget type to select by default.") + (@arg DEFAULT_WIDGET_COUNT: --default_widget_count +takes_value "Which number of the selected widget type to select, from left to right, top to bottom. Defaults to 1.") //(@arg TURNED_OFF_CPUS: -t ... +takes_value "Hides CPU data points by default") // TODO: [FEATURE] Enable disabling cores in config/flags ) .get_matches() @@ -105,14 +99,11 @@ fn main() -> error::Result<()> { let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; - // Create "app" struct, which will control most of the program and store settings/state - let mut app = build_app(&matches, &config)?; + // Get widget layout separately + let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?; - // TODO: [REFACTOR] Change this - enable_app_grouping(&matches, &config, &mut app); - enable_app_case_sensitive(&matches, &config, &mut app); - enable_app_match_whole_word(&matches, &config, &mut app); - enable_app_use_regex(&matches, &config, &mut app); + // Create "app" struct, which will control most of the program and store settings/state + let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?; // Set up up tui and crossterm let mut stdout_val = stdout(); @@ -150,13 +141,13 @@ fn main() -> error::Result<()> { app.app_config_fields.show_average_cpu, ); - let mut painter = canvas::Painter::default(); + let mut painter = canvas::Painter::init(widget_layout); if let Err(config_check) = generate_config_colours(&config, &mut painter) { cleanup_terminal(&mut terminal)?; return Err(config_check); } painter.colours.generate_remaining_cpu_colours(); - painter.initialize(); + painter.complete_painter_init(); let mut first_run = true; loop { @@ -179,11 +170,7 @@ fn main() -> error::Result<()> { // Convert all data into tui-compliant components // Network - let network_data = convert_network_data_points( - &app.data_collection, - app.net_state.current_display_time, - false, - ); + let network_data = convert_network_data_points(&app.data_collection, false); app.canvas_data.network_data_rx = network_data.rx; app.canvas_data.network_data_tx = network_data.tx; app.canvas_data.rx_display = network_data.rx_display; @@ -196,17 +183,12 @@ fn main() -> error::Result<()> { // Temperatures app.canvas_data.temp_sensor_data = convert_temp_row(&app); + // Memory - app.canvas_data.mem_data = convert_mem_data_points( - &app.data_collection, - app.mem_state.current_display_time, - false, - ); - app.canvas_data.swap_data = convert_swap_data_points( - &app.data_collection, - app.mem_state.current_display_time, - false, - ); + app.canvas_data.mem_data = + convert_mem_data_points(&app.data_collection, false); + app.canvas_data.swap_data = + convert_swap_data_points(&app.data_collection, false); let memory_and_swap_labels = convert_mem_labels(&app.data_collection); app.canvas_data.mem_label = memory_and_swap_labels.0; app.canvas_data.swap_label = memory_and_swap_labels.1; @@ -214,23 +196,23 @@ fn main() -> error::Result<()> { // Pre-fill CPU if needed if first_run { let cpu_len = app.data_collection.cpu_harvest.len(); - app.cpu_state.core_show_vec = vec![true; cpu_len]; - app.cpu_state.num_cpus_shown = cpu_len as u64; + app.cpu_state.widget_states.values_mut().for_each(|state| { + state.core_show_vec = vec![true; cpu_len]; + state.num_cpus_shown = cpu_len; + }); + app.cpu_state.num_cpus_total = cpu_len; first_run = false; } // CPU - app.canvas_data.cpu_data = convert_cpu_data_points( - &app.data_collection, - app.cpu_state.current_display_time, - false, - ); + app.canvas_data.cpu_data = + convert_cpu_data_points(&app.data_collection, false); // Processes let (single, grouped) = convert_process_data(&app.data_collection); app.canvas_data.process_data = single; app.canvas_data.grouped_process_data = grouped; - update_final_process_list(&mut app); + update_all_process_lists(&mut app); } } BottomEvent::Clean => { @@ -240,14 +222,6 @@ fn main() -> error::Result<()> { } } - // Quick fix for tab updating the table headers - if let data_harvester::processes::ProcessSorting::PID = &app.process_sorting_type { - if app.is_grouped() { - app.process_sorting_type = data_harvester::processes::ProcessSorting::CPU; // Go back to default, negate PID for group - app.process_sorting_reverse = true; - } - } - try_drawing(&mut terminal, &mut app, &mut painter)?; } @@ -292,19 +266,13 @@ fn handle_key_event_or_break( KeyCode::Backspace => app.on_backspace(), KeyCode::Delete => app.on_delete(), KeyCode::F(1) => { - if app.is_in_search_widget() { - app.toggle_ignore_case(); - } + app.toggle_ignore_case(); } KeyCode::F(2) => { - if app.is_in_search_widget() { - app.toggle_search_whole_word(); - } + app.toggle_search_whole_word(); } KeyCode::F(3) => { - if app.is_in_search_widget() { - app.toggle_search_regex(); - } + app.toggle_search_regex(); } _ => {} } @@ -313,19 +281,13 @@ fn handle_key_event_or_break( if let KeyModifiers::ALT = event.modifiers { match event.code { KeyCode::Char('c') | KeyCode::Char('C') => { - if app.is_in_search_widget() { - app.toggle_ignore_case(); - } + app.toggle_ignore_case(); } KeyCode::Char('w') | KeyCode::Char('W') => { - if app.is_in_search_widget() { - app.toggle_search_whole_word(); - } + app.toggle_search_whole_word(); } KeyCode::Char('r') | KeyCode::Char('R') => { - if app.is_in_search_widget() { - app.toggle_search_regex(); - } + app.toggle_search_regex(); } _ => {} } @@ -550,59 +512,65 @@ fn panic_hook(panic_info: &PanicInfo<'_>) { } fn handle_force_redraws(app: &mut App) { - if app.force_update_processes { - update_final_process_list(app); - app.force_update_processes = false; + // Currently we use an Option... because we might want to future-proof this + // if we eventually get widget-specific redrawing! + if app.proc_state.force_update_all { + update_all_process_lists(app); + app.proc_state.force_update_all = false; + } else if let Some(widget_id) = app.proc_state.force_update { + update_final_process_list(app, widget_id); + app.proc_state.force_update = None; } - if app.cpu_state.force_update { - app.canvas_data.cpu_data = convert_cpu_data_points( - &app.data_collection, - app.cpu_state.current_display_time, - app.is_frozen, - ); - app.cpu_state.force_update = false; + if app.cpu_state.force_update.is_some() { + app.canvas_data.cpu_data = convert_cpu_data_points(&app.data_collection, app.is_frozen); + app.cpu_state.force_update = None; } - if app.mem_state.force_update { - app.canvas_data.mem_data = convert_mem_data_points( - &app.data_collection, - app.mem_state.current_display_time, - app.is_frozen, - ); - app.canvas_data.swap_data = convert_swap_data_points( - &app.data_collection, - app.mem_state.current_display_time, - app.is_frozen, - ); - app.mem_state.force_update = false; + if app.mem_state.force_update.is_some() { + app.canvas_data.mem_data = convert_mem_data_points(&app.data_collection, app.is_frozen); + app.canvas_data.swap_data = convert_swap_data_points(&app.data_collection, app.is_frozen); + app.mem_state.force_update = None; } - if app.net_state.force_update { - let (rx, tx) = get_rx_tx_data_points( - &app.data_collection, - app.net_state.current_display_time, - app.is_frozen, - ); + if app.net_state.force_update.is_some() { + let (rx, tx) = get_rx_tx_data_points(&app.data_collection, app.is_frozen); app.canvas_data.network_data_rx = rx; app.canvas_data.network_data_tx = tx; - app.net_state.force_update = false; + app.net_state.force_update = None; } } -fn update_final_process_list(app: &mut App) { - let mut filtered_process_data: Vec = if app.is_grouped() { +fn update_all_process_lists(app: &mut App) { + let widget_ids = app + .proc_state + .widget_states + .keys() + .cloned() + .collect::>(); + + widget_ids.into_iter().for_each(|widget_id| { + update_final_process_list(app, widget_id); + }); +} + +fn update_final_process_list(app: &mut App, widget_id: u64) { + let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) { + Some(process_state) => process_state + .process_search_state + .search_state + .is_invalid_or_blank_search(), + None => false, + }; + + let filtered_process_data: Vec = if app.is_grouped(widget_id) { app.canvas_data .grouped_process_data .iter() .filter(|process| { - if app - .process_search_state - .search_state - .is_invalid_or_blank_search() - { + if is_invalid_or_blank { return true; - } else if let Some(matcher_result) = app.get_current_regex_matcher() { + } else if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) { if let Ok(matcher) = matcher_result { return matcher.is_match(&process.name); } @@ -613,20 +581,20 @@ fn update_final_process_list(app: &mut App) { .cloned() .collect::>() } else { + let is_searching_with_pid = match app.proc_state.widget_states.get(&widget_id) { + Some(process_state) => process_state.process_search_state.is_searching_with_pid, + None => false, + }; + app.canvas_data .process_data .iter() .filter_map(|(_pid, process)| { let mut result = true; - - if !app - .process_search_state - .search_state - .is_invalid_or_blank_search() - { - if let Some(matcher_result) = app.get_current_regex_matcher() { + if !is_invalid_or_blank { + if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) { if let Ok(matcher) = matcher_result { - if app.process_search_state.is_searching_with_pid { + if is_searching_with_pid { result = matcher.is_match(&process.pid.to_string()); } else { result = matcher.is_match(&process.name); @@ -650,31 +618,84 @@ fn update_final_process_list(app: &mut App) { .collect::>() }; - sort_process_data(&mut filtered_process_data, app); - app.canvas_data.finalized_process_data = filtered_process_data; + // Quick fix for tab updating the table headers + if let Some(proc_widget_state) = app.proc_state.widget_states.get_mut(&widget_id) { + if let data_harvester::processes::ProcessSorting::PID = + proc_widget_state.process_sorting_type + { + if proc_widget_state.is_grouped { + proc_widget_state.process_sorting_type = + data_harvester::processes::ProcessSorting::CPU; // Go back to default, negate PID for group + proc_widget_state.process_sorting_reverse = true; + } + } + + let mut resulting_processes = filtered_process_data; + sort_process_data(&mut resulting_processes, proc_widget_state); + + if proc_widget_state.scroll_state.current_scroll_position + >= resulting_processes.len() as u64 + { + proc_widget_state.scroll_state.current_scroll_position = + if resulting_processes.len() > 1 { + resulting_processes.len() as u64 - 1 + } else { + 0 + }; + proc_widget_state.scroll_state.previous_scroll_position = 0; + proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::DOWN; + } + + app.canvas_data + .finalized_process_data_map + .insert(widget_id, resulting_processes); + } } -fn sort_process_data(to_sort_vec: &mut Vec, app: &App) { +fn sort_process_data( + to_sort_vec: &mut Vec, proc_widget_state: &app::ProcWidgetState, +) { to_sort_vec.sort_by(|a, b| utils::gen_util::get_ordering(&a.name, &b.name, false)); - match app.process_sorting_type { + match proc_widget_state.process_sorting_type { ProcessSorting::CPU => { to_sort_vec.sort_by(|a, b| { - utils::gen_util::get_ordering(a.cpu_usage, b.cpu_usage, app.process_sorting_reverse) + utils::gen_util::get_ordering( + a.cpu_usage, + b.cpu_usage, + proc_widget_state.process_sorting_reverse, + ) }); } ProcessSorting::MEM => { to_sort_vec.sort_by(|a, b| { - utils::gen_util::get_ordering(a.mem_usage, b.mem_usage, app.process_sorting_reverse) + utils::gen_util::get_ordering( + a.mem_usage, + b.mem_usage, + proc_widget_state.process_sorting_reverse, + ) }); } - ProcessSorting::NAME => to_sort_vec.sort_by(|a, b| { - utils::gen_util::get_ordering(&a.name, &b.name, app.process_sorting_reverse) - }), - ProcessSorting::PID => { - if !app.is_grouped() { + ProcessSorting::NAME => { + // Don't repeat if false... + if proc_widget_state.process_sorting_reverse { to_sort_vec.sort_by(|a, b| { - utils::gen_util::get_ordering(a.pid, b.pid, app.process_sorting_reverse) + utils::gen_util::get_ordering( + &a.name, + &b.name, + proc_widget_state.process_sorting_reverse, + ) + }) + } + } + ProcessSorting::PID => { + if !proc_widget_state.is_grouped { + to_sort_vec.sort_by(|a, b| { + utils::gen_util::get_ordering( + a.pid, + b.pid, + proc_widget_state.process_sorting_reverse, + ) }); } } diff --git a/src/options.rs b/src/options.rs index b631f903..96e1e80a 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,21 +1,26 @@ use serde::Deserialize; - +use std::collections::HashMap; use std::time::Instant; use crate::{ - app::{data_harvester, App, AppConfigFields, CpuState, MemState, NetState, WidgetPosition}, + app::{ + data_harvester, layout_manager::*, App, AppConfigFields, BasicTableWidgetState, CpuState, + CpuWidgetState, DiskState, DiskWidgetState, MemState, MemWidgetState, NetState, + NetWidgetState, ProcState, ProcWidgetState, TempState, TempWidgetState, + }, constants::*, utils::error::{self, BottomError}, }; -// use layout_manager::*; +use layout_options::*; -// mod layout_manager; +mod layout_options; #[derive(Default, Deserialize)] pub struct Config { pub flags: Option, pub colors: Option, + pub row: Option>, } #[derive(Default, Deserialize)] @@ -37,6 +42,8 @@ pub struct ConfigFlags { pub time_delta: Option, pub autohide_time: Option, pub hide_time: Option, + pub default_widget_type: Option, + pub default_widget_count: Option, //disabled_cpu_cores: Option>, // TODO: [FEATURE] Enable disabling cores in config/flags } @@ -60,27 +67,135 @@ pub struct ConfigColours { pub graph_color: Option, } -pub fn build_app(matches: &clap::ArgMatches<'static>, config: &Config) -> error::Result { +pub fn build_app( + matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout, + default_widget_id: u64, +) -> error::Result { let autohide_time = get_autohide_time(&matches, &config); let default_time_value = get_default_time_value(&matches, &config)?; - let default_widget = get_default_widget(&matches, &config); let use_basic_mode = get_use_basic_mode(&matches, &config); - let current_widget_selected = if use_basic_mode { - match default_widget { - WidgetPosition::Cpu => WidgetPosition::BasicCpu, - WidgetPosition::Network => WidgetPosition::BasicNet, - WidgetPosition::Mem => WidgetPosition::BasicMem, - _ => default_widget, - } + // For processes + let is_grouped = get_app_grouping(matches, config); + let is_case_sensitive = get_app_case_sensitive(matches, config); + let is_match_whole_word = get_app_match_whole_word(matches, config); + let is_use_regex = get_app_use_regex(matches, config); + + let mut widget_map = HashMap::new(); + let mut cpu_state_map: HashMap = HashMap::new(); + let mut mem_state_map: HashMap = HashMap::new(); + let mut net_state_map: HashMap = HashMap::new(); + let mut proc_state_map: HashMap = HashMap::new(); + let mut temp_state_map: HashMap = HashMap::new(); + let mut disk_state_map: HashMap = HashMap::new(); + + let autohide_timer = if autohide_time { + Some(Instant::now()) } else { - default_widget + None }; - let previous_basic_table_selected = if default_widget.is_widget_table() { - default_widget + let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?; + let mut initial_widget_id: u64 = default_widget_id; + let mut initial_widget_type = BottomWidgetType::Proc; + let is_custom_layout = config.row.is_some(); + + for row in &widget_layout.rows { + for col in &row.children { + for col_row in &col.children { + for widget in &col_row.children { + widget_map.insert(widget.widget_id, widget.clone()); + if let Some(default_widget_type) = &default_widget_type_option { + if !is_custom_layout || use_basic_mode { + match widget.widget_type { + BottomWidgetType::BasicCpu => { + if let BottomWidgetType::Cpu = *default_widget_type { + initial_widget_id = widget.widget_id; + initial_widget_type = BottomWidgetType::Cpu; + } + } + BottomWidgetType::BasicMem => { + if let BottomWidgetType::Mem = *default_widget_type { + initial_widget_id = widget.widget_id; + initial_widget_type = BottomWidgetType::Cpu; + } + } + BottomWidgetType::BasicNet => { + if let BottomWidgetType::Net = *default_widget_type { + initial_widget_id = widget.widget_id; + initial_widget_type = BottomWidgetType::Cpu; + } + } + _ => { + if *default_widget_type == widget.widget_type { + initial_widget_id = widget.widget_id; + initial_widget_type = widget.widget_type.clone(); + } + } + } + } + } + match widget.widget_type { + BottomWidgetType::Cpu => { + cpu_state_map.insert( + widget.widget_id, + CpuWidgetState::init(default_time_value, autohide_timer), + ); + } + BottomWidgetType::Mem => { + mem_state_map.insert( + widget.widget_id, + MemWidgetState::init(default_time_value, autohide_timer), + ); + } + BottomWidgetType::Net => { + net_state_map.insert( + widget.widget_id, + NetWidgetState::init(default_time_value, autohide_timer), + ); + } + BottomWidgetType::Proc => { + proc_state_map.insert( + widget.widget_id, + ProcWidgetState::init( + is_case_sensitive, + is_match_whole_word, + is_use_regex, + is_grouped, + ), + ); + } + BottomWidgetType::Disk => { + disk_state_map.insert(widget.widget_id, DiskWidgetState::init()); + } + BottomWidgetType::Temp => { + temp_state_map.insert(widget.widget_id, TempWidgetState::init()); + } + _ => {} + } + } + } + } + } + + // FIXME: [MODULARITY] Don't collect if not added! + let basic_table_widget_state = if use_basic_mode { + Some(match initial_widget_type { + BottomWidgetType::Proc | BottomWidgetType::Disk | BottomWidgetType::Temp => { + BasicTableWidgetState { + currently_displayed_widget_type: initial_widget_type, + currently_displayed_widget_id: initial_widget_id, + widget_id: 100, + } + } + _ => BasicTableWidgetState { + currently_displayed_widget_type: BottomWidgetType::Proc, + currently_displayed_widget_id: DEFAULT_WIDGET_ID, + widget_id: 100, + }, + }) } else { - WidgetPosition::Process + None }; let app_config_fields = AppConfigFields { @@ -98,22 +213,62 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &Config) -> error: autohide_time, }; - let time_now = if autohide_time { - Some(Instant::now()) - } else { - None - }; - Ok(App::builder() .app_config_fields(app_config_fields) - .current_widget_selected(current_widget_selected) - .previous_basic_table_selected(previous_basic_table_selected) - .cpu_state(CpuState::init(default_time_value, time_now)) - .mem_state(MemState::init(default_time_value, time_now)) - .net_state(NetState::init(default_time_value, time_now)) + .cpu_state(CpuState::init(cpu_state_map)) + .mem_state(MemState::init(mem_state_map)) + .net_state(NetState::init(net_state_map)) + .proc_state(ProcState::init(proc_state_map)) + .disk_state(DiskState::init(disk_state_map)) + .temp_state(TempState::init(temp_state_map)) + .basic_table_widget_state(basic_table_widget_state) + .current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // I think the unwrap is fine here + .widget_map(widget_map) .build()) } +pub fn get_widget_layout( + matches: &clap::ArgMatches<'static>, config: &Config, +) -> error::Result<(BottomLayout, u64)> { + let left_legend = get_use_left_legend(matches, config); + let (default_widget_type, mut default_widget_count) = + get_default_widget_and_count(matches, config)?; + let mut default_widget_id = 1; + + let bottom_layout = if get_use_basic_mode(matches, config) { + default_widget_id = DEFAULT_WIDGET_ID; + BottomLayout::init_basic_default() + } else if let Some(rows) = &config.row { + let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs* + let mut total_height_ratio = 0; + + let mut ret_bottom_layout = BottomLayout { + rows: rows + .iter() + .map(|row| { + row.convert_row_to_bottom_row( + &mut iter_id, + &mut total_height_ratio, + &mut default_widget_id, + &default_widget_type, + &mut default_widget_count, + left_legend, + ) + }) + .collect::>>()?, + total_row_height_ratio: total_height_ratio, + }; + ret_bottom_layout.get_movement_mappings(); + + ret_bottom_layout + } else { + default_widget_id = DEFAULT_WIDGET_ID; + BottomLayout::init_default(left_legend) + }; + + Ok((bottom_layout, default_widget_id)) +} + fn get_update_rate_in_milliseconds( matches: &clap::ArgMatches<'static>, config: &Config, ) -> error::Result { @@ -296,56 +451,56 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er Ok(time_interval as u64) } -pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { +pub fn get_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("GROUP_PROCESSES") { - app.toggle_grouping(); + return true; } else if let Some(flags) = &config.flags { if let Some(grouping) = flags.group_processes { if grouping { - app.toggle_grouping(); + return true; } } } + false } -pub fn enable_app_case_sensitive( - matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App, -) { +pub fn get_app_case_sensitive(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("CASE_SENSITIVE") { - app.process_search_state.search_toggle_ignore_case(); + return true; } else if let Some(flags) = &config.flags { if let Some(case_sensitive) = flags.case_sensitive { if case_sensitive { - app.process_search_state.search_toggle_ignore_case(); + return true; } } } + false } -pub fn enable_app_match_whole_word( - matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App, -) { +pub fn get_app_match_whole_word(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("WHOLE_WORD") { - app.process_search_state.search_toggle_whole_word(); + return true; } else if let Some(flags) = &config.flags { if let Some(whole_word) = flags.whole_word { if whole_word { - app.process_search_state.search_toggle_whole_word(); + return true; } } } + false } -pub fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { +pub fn get_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("REGEX_DEFAULT") { - app.process_search_state.search_toggle_regex(); + return true; } else if let Some(flags) = &config.flags { if let Some(regex) = flags.regex { if regex { - app.process_search_state.search_toggle_regex(); + return true; } } } + false } fn get_hide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { @@ -375,32 +530,52 @@ fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bo false } -fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition { - if matches.is_present("CPU_WIDGET") { - return WidgetPosition::Cpu; - } else if matches.is_present("MEM_WIDGET") { - return WidgetPosition::Mem; - } else if matches.is_present("DISK_WIDGET") { - return WidgetPosition::Disk; - } else if matches.is_present("TEMP_WIDGET") { - return WidgetPosition::Temp; - } else if matches.is_present("NET_WIDGET") { - return WidgetPosition::Network; - } else if matches.is_present("PROC_WIDGET") { - return WidgetPosition::Process; - } else if let Some(flags) = &config.flags { - if let Some(default_widget) = &flags.default_widget { - return match default_widget.as_str() { - "cpu_default" => WidgetPosition::Cpu, - "memory_default" => WidgetPosition::Mem, - "processes_default" => WidgetPosition::Process, - "network_default" => WidgetPosition::Network, - "temperature_default" => WidgetPosition::Temp, - "disk_default" => WidgetPosition::Disk, - _ => WidgetPosition::Process, - }; +fn get_default_widget_and_count( + matches: &clap::ArgMatches<'static>, config: &Config, +) -> error::Result<(Option, u64)> { + let widget_type = if let Some(widget_type) = matches.value_of("DEFAULT_WIDGET_TYPE") { + let parsed_widget = widget_type.parse::()?; + if let BottomWidgetType::Empty = parsed_widget { + None + } else { + Some(parsed_widget) } - } + } else if let Some(flags) = &config.flags { + if let Some(widget_type) = &flags.default_widget_type { + let parsed_widget = widget_type.parse::()?; + if let BottomWidgetType::Empty = parsed_widget { + None + } else { + Some(parsed_widget) + } + } else { + None + } + } else { + None + }; - WidgetPosition::Process + if widget_type.is_some() { + let widget_count = if let Some(widget_count) = matches.value_of("DEFAULT_WIDGET_COUNT") { + widget_count.parse::()? + } else if let Some(flags) = &config.flags { + if let Some(widget_count) = flags.default_widget_count { + widget_count as u128 + } else { + 1 as u128 + } + } else { + 1 as u128 + }; + + if widget_count > std::u64::MAX as u128 { + Err(BottomError::InvalidArg( + "Please set your widget count to be at most unsigned INT_MAX.".to_string(), + )) + } else { + Ok((widget_type, widget_count as u64)) + } + } else { + Ok((None, 1)) + } } diff --git a/src/options/layout_manager.rs b/src/options/layout_manager.rs deleted file mode 100644 index d13ac5f9..00000000 --- a/src/options/layout_manager.rs +++ /dev/null @@ -1,3 +0,0 @@ -use serde::Deserialize; -use toml::Value; - diff --git a/src/options/layout_options.rs b/src/options/layout_options.rs new file mode 100644 index 00000000..adf76c2e --- /dev/null +++ b/src/options/layout_options.rs @@ -0,0 +1,307 @@ +use crate::app::layout_manager::*; +use crate::error::Result; +use serde::Deserialize; + +/// Represents a row. This has a length of some sort (optional) and a vector +/// of children. +#[derive(Deserialize, Debug)] +#[serde(rename = "row")] +pub struct Row { + pub ratio: Option, + pub child: Option>, +} + +impl Row { + pub fn convert_row_to_bottom_row( + &self, iter_id: &mut u64, total_height_ratio: &mut u32, default_widget_id: &mut u64, + default_widget_type: &Option, default_widget_count: &mut u64, + left_legend: bool, + ) -> Result { + // In the future we want to also add percentages. + // But for MVP, we aren't going to bother. + let row_ratio = self.ratio.unwrap_or(1); + let mut children = Vec::new(); + + *total_height_ratio += row_ratio; + + let mut total_col_ratio = 0; + if let Some(row_children) = &self.child { + for row_child in row_children { + match row_child { + RowChildren::Widget(widget) => { + *iter_id += 1; + let width_ratio = widget.ratio.unwrap_or(1); + total_col_ratio += width_ratio; + let widget_type = widget.widget_type.parse::()?; + + if let Some(default_widget_type_val) = default_widget_type { + if *default_widget_type_val == widget_type && *default_widget_count > 0 + { + *default_widget_count -= 1; + if *default_widget_count == 0 { + *default_widget_id = *iter_id; + } + } + } else { + // Check default flag + if let Some(default_widget_flag) = widget.default { + if default_widget_flag { + *default_widget_id = *iter_id; + } + } + } + + children.push(match widget_type { + BottomWidgetType::Cpu => { + let iter_old_id = *iter_id; + *iter_id += 1; + BottomCol::builder() + .col_width_ratio(width_ratio) + .children(if left_legend { + vec![BottomColRow::builder() + .total_widget_ratio(20) + .children(vec![ + BottomWidget::builder() + .width_ratio(3) + .widget_type(BottomWidgetType::CpuLegend) + .widget_id(*iter_id) + .canvas_handle_width(true) + .build(), + BottomWidget::builder() + .width_ratio(17) + .widget_type(BottomWidgetType::Cpu) + .widget_id(iter_old_id) + .flex_grow(true) + .build(), + ]) + .build()] + } else { + vec![BottomColRow::builder() + .total_widget_ratio(20) + .children(vec![ + BottomWidget::builder() + .width_ratio(17) + .widget_type(BottomWidgetType::Cpu) + .widget_id(iter_old_id) + .flex_grow(true) + .build(), + BottomWidget::builder() + .width_ratio(3) + .widget_type(BottomWidgetType::CpuLegend) + .widget_id(*iter_id) + .canvas_handle_width(true) + .build(), + ]) + .build()] + }) + .build() + } + BottomWidgetType::Proc => { + let iter_old_id = *iter_id; + *iter_id += 1; + BottomCol::builder() + .total_col_row_ratio(2) + .col_width_ratio(width_ratio) + .children(vec![ + BottomColRow::builder() + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Proc) + .widget_id(iter_old_id) + .build()]) + .flex_grow(true) + .build(), + BottomColRow::builder() + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::ProcSearch) + .widget_id(*iter_id) + .build()]) + .canvas_handle_height(true) + .build(), + ]) + .build() + } + _ => BottomCol::builder() + .col_width_ratio(width_ratio) + .children(vec![BottomColRow::builder() + .children(vec![BottomWidget::builder() + .widget_type(widget_type) + .widget_id(*iter_id) + .build()]) + .build()]) + .build(), + }); + } + RowChildren::Col { ratio, child } => { + let col_width_ratio = ratio.unwrap_or(1); + total_col_ratio += col_width_ratio; + let mut total_col_row_ratio = 0; + let mut contains_proc = false; + + let mut col_row_children = Vec::new(); + + for widget in child { + let widget_type = widget.widget_type.parse::()?; + *iter_id += 1; + let col_row_height_ratio = widget.ratio.unwrap_or(1); + total_col_row_ratio += col_row_height_ratio; + + if let Some(default_widget_type_val) = default_widget_type { + if *default_widget_type_val == widget_type + && *default_widget_count > 0 + { + *default_widget_count -= 1; + if *default_widget_count == 0 { + *default_widget_id = *iter_id; + } + } + } else { + // Check default flag + if let Some(default_widget_flag) = widget.default { + if default_widget_flag { + *default_widget_id = *iter_id; + } + } + } + + match widget_type { + BottomWidgetType::Cpu => { + let iter_old_id = *iter_id; + *iter_id += 1; + if left_legend { + col_row_children.push( + BottomColRow::builder() + .col_row_height_ratio(col_row_height_ratio) + .total_widget_ratio(20) + .children(vec![ + BottomWidget::builder() + .width_ratio(3) + .widget_type(BottomWidgetType::CpuLegend) + .widget_id(*iter_id) + .canvas_handle_width(true) + .build(), + BottomWidget::builder() + .width_ratio(17) + .widget_type(BottomWidgetType::Cpu) + .widget_id(iter_old_id) + .flex_grow(true) + .build(), + ]) + .build(), + ); + } else { + col_row_children.push( + BottomColRow::builder() + .col_row_height_ratio(col_row_height_ratio) + .total_widget_ratio(20) + .children(vec![ + BottomWidget::builder() + .width_ratio(17) + .widget_type(BottomWidgetType::Cpu) + .widget_id(iter_old_id) + .flex_grow(true) + .build(), + BottomWidget::builder() + .width_ratio(3) + .widget_type(BottomWidgetType::CpuLegend) + .widget_id(*iter_id) + .canvas_handle_width(true) + .build(), + ]) + .build(), + ); + } + } + BottomWidgetType::Proc => { + contains_proc = true; + let iter_old_id = *iter_id; + *iter_id += 1; + col_row_children.push( + BottomColRow::builder() + .col_row_height_ratio(col_row_height_ratio) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::Proc) + .widget_id(iter_old_id) + .build()]) + .flex_grow(true) + .build(), + ); + col_row_children.push( + BottomColRow::builder() + .col_row_height_ratio(col_row_height_ratio) + .children(vec![BottomWidget::builder() + .widget_type(BottomWidgetType::ProcSearch) + .widget_id(*iter_id) + .build()]) + .canvas_handle_height(true) + .build(), + ); + } + _ => col_row_children.push( + BottomColRow::builder() + .col_row_height_ratio(col_row_height_ratio) + .children(vec![BottomWidget::builder() + .widget_type(widget_type) + .widget_id(*iter_id) + .build()]) + .build(), + ), + } + } + + if contains_proc { + // Must adjust ratios to work with proc + total_col_row_ratio *= 2; + for child in &mut col_row_children { + // Multiply all non-proc or proc-search ratios by 2 + if !child.children.is_empty() { + match child.children[0].widget_type { + BottomWidgetType::Proc | BottomWidgetType::ProcSearch => {} + _ => child.col_row_height_ratio *= 2, + } + } + } + } + + children.push( + BottomCol::builder() + .total_col_row_ratio(total_col_row_ratio) + .col_width_ratio(col_width_ratio) + .children(col_row_children) + .build(), + ); + } + } + } + } + + Ok(BottomRow::builder() + .total_col_ratio(total_col_ratio) + .row_height_ratio(row_ratio) + .children(children) + .build()) + } +} + +/// Represents a child of a Row - either a Col (column) or a FinalWidget. +/// +/// A Col can also have an optional length and children. We only allow columns +/// to have FinalWidgets as children, lest we get some amount of mutual +/// recursion between Row and Col. +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum RowChildren { + Widget(FinalWidget), + Col { + ratio: Option, + child: Vec, + }, +} + +/// Represents a widget. +#[derive(Deserialize, Debug)] +pub struct FinalWidget { + pub ratio: Option, + #[serde(rename = "type")] + pub widget_type: String, + pub default: Option, +} diff --git a/tests/arg_tests.rs b/tests/arg_tests.rs index 627b1cbe..0cf0cd45 100644 --- a/tests/arg_tests.rs +++ b/tests/arg_tests.rs @@ -141,14 +141,28 @@ fn test_conflicting_temps() -> Result<(), Box> { } #[test] -fn test_conflicting_default_widget() -> Result<(), Box> { +fn test_invalid_default_widget_1() -> Result<(), Box> { Command::new(get_os_binary_loc()) - .arg("--cpu_default") - .arg("--disk_default") + .arg("--default_widget_type") + .arg("fake_widget") + .assert() + .failure() + .stderr(predicate::str::contains("Invalid widget type")); + + Ok(()) +} + +#[test] +fn test_invalid_default_widget_2() -> Result<(), Box> { + Command::new(get_os_binary_loc()) + .arg("--default_widget_type") + .arg("cpu") + .arg("--default_widget_count") + .arg("18446744073709551616") .assert() .failure() .stderr(predicate::str::contains( - "cannot be used with one or more of the other specified arguments", + "Please set your widget count to be at most unsigned INT_MAX", )); Ok(())