From 6d0b7035d39f56d5e4ab5dfade644db8d36db102 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 8 Mar 2020 16:17:28 -0400 Subject: [PATCH 1/9] Redid basic mode logic and separated CPU * Separated CPU into CPU legend and graph * Redid how I did maximizing with basic mode --- src/app.rs | 126 ++++++++++++++++++++++------ src/canvas.rs | 6 +- src/canvas/widgets/cpu_graph.rs | 37 ++++---- src/canvas/widgets/mem_graph.rs | 4 +- src/canvas/widgets/network_graph.rs | 4 +- src/main.rs | 4 +- 6 files changed, 126 insertions(+), 55 deletions(-) diff --git a/src/app.rs b/src/app.rs index 8033d227..4d536262 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,10 +17,12 @@ const MAX_SEARCH_LENGTH: usize = 200; #[derive(Debug, Clone, Copy)] pub enum WidgetPosition { Cpu, + CpuLegend, Mem, Disk, Temp, Network, + NetworkLegend, Process, ProcessSearch, BasicCpu, @@ -34,19 +36,28 @@ impl WidgetPosition { WidgetPosition::Disk | WidgetPosition::Process | WidgetPosition::ProcessSearch - | WidgetPosition::Temp => true, + | 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 { - WidgetPosition::Cpu | WidgetPosition::BasicCpu => "CPU", - WidgetPosition::Mem | WidgetPosition::BasicMem => "Memory", - WidgetPosition::Disk => "Disks", - WidgetPosition::Temp => "Temperature", - WidgetPosition::Network | WidgetPosition::BasicNet => "Network", - WidgetPosition::Process | WidgetPosition::ProcessSearch => "Processes", + Cpu | BasicCpu | CpuLegend => "CPU", + Mem | BasicMem => "Memory", + Disk => "Disks", + Temp => "Temperature", + Network | BasicNet | NetworkLegend => "Network", + Process | ProcessSearch => "Processes", } .to_string() } @@ -375,10 +386,7 @@ impl App { self.dd_err = None; } else if self.is_filtering_or_searching() { match self.current_widget_selected { - WidgetPosition::Cpu - if self.is_expanded && self.app_config_fields.use_basic_mode => - { - self.current_widget_selected = WidgetPosition::BasicCpu; + WidgetPosition::Cpu | WidgetPosition::CpuLegend => { self.cpu_state.is_showing_tray = false; } WidgetPosition::Process | WidgetPosition::ProcessSearch => { @@ -387,9 +395,6 @@ impl App { self.process_search_state.search_state.is_enabled = false; } } - WidgetPosition::Cpu => { - self.cpu_state.is_showing_tray = false; - } WidgetPosition::Mem => { self.mem_state.is_showing_tray = false; } @@ -401,14 +406,22 @@ impl App { } 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, + } + } } } fn is_filtering_or_searching(&self) -> bool { match self.current_widget_selected { - WidgetPosition::Cpu => self.cpu_state.is_showing_tray, - WidgetPosition::Mem => self.mem_state.is_showing_tray, - WidgetPosition::Network => self.net_state.is_showing_tray, + 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 } @@ -464,7 +477,7 @@ impl App { pub fn on_space(&mut self) { match self.current_widget_selected { - WidgetPosition::Cpu => { + WidgetPosition::CpuLegend => { let curr_posn = self .app_scroll_positions .cpu_scroll_state @@ -496,8 +509,9 @@ impl App { self.search_with_name(); } } - WidgetPosition::Cpu => { + 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; @@ -650,6 +664,15 @@ impl App { 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, + } + } } } @@ -1065,6 +1088,9 @@ impl App { 'K' => self.move_widget_selection_up(), 'J' => self.move_widget_selection_down(), ' ' => self.on_space(), + '+' => {} + '-' => {} + '=' => {} _ => {} } @@ -1124,6 +1150,12 @@ impl App { }; } else { self.current_widget_selected = match self.current_widget_selected { + WidgetPosition::Cpu if self.app_config_fields.left_legend => { + WidgetPosition::CpuLegend + } + WidgetPosition::CpuLegend if !self.app_config_fields.left_legend => { + WidgetPosition::Cpu + } WidgetPosition::Process => WidgetPosition::Network, WidgetPosition::ProcessSearch => WidgetPosition::Network, WidgetPosition::Disk => WidgetPosition::Mem, @@ -1131,6 +1163,16 @@ impl App { _ => 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 + } + WidgetPosition::CpuLegend if !self.app_config_fields.left_legend => { + WidgetPosition::Cpu + } + _ => self.current_widget_selected, + } } self.reset_multi_tap_keys(); @@ -1149,11 +1191,27 @@ impl App { }; } else { self.current_widget_selected = match self.current_widget_selected { + WidgetPosition::Cpu if !self.app_config_fields.left_legend => { + WidgetPosition::CpuLegend + } + 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 + } + WidgetPosition::CpuLegend if self.app_config_fields.left_legend => { + WidgetPosition::Cpu + } + _ => self.current_widget_selected, + } } self.reset_multi_tap_keys(); @@ -1213,7 +1271,7 @@ impl App { }; } else { self.current_widget_selected = match self.current_widget_selected { - WidgetPosition::Cpu => WidgetPosition::Mem, + WidgetPosition::Cpu | WidgetPosition::CpuLegend => WidgetPosition::Mem, WidgetPosition::Mem => WidgetPosition::Network, WidgetPosition::Temp => WidgetPosition::Disk, WidgetPosition::Disk => WidgetPosition::Process, @@ -1261,7 +1319,7 @@ impl App { .disk_scroll_state .current_scroll_position = 0 } - WidgetPosition::Cpu => { + WidgetPosition::CpuLegend => { self.app_scroll_positions .cpu_scroll_state .current_scroll_position = 0 @@ -1294,7 +1352,7 @@ impl App { .disk_scroll_state .current_scroll_position = self.canvas_data.disk_data.len() as u64 - 1 } - WidgetPosition::Cpu => { + WidgetPosition::CpuLegend => { self.app_scroll_positions .cpu_scroll_state .current_scroll_position = self.canvas_data.cpu_data.len() as u64 - 1; @@ -1312,7 +1370,7 @@ impl App { WidgetPosition::Process => self.change_process_position(-1), WidgetPosition::Temp => self.change_temp_position(-1), WidgetPosition::Disk => self.change_disk_position(-1), - WidgetPosition::Cpu => self.change_cpu_table_position(-1), // TODO: [PO?] Temporary, may change if we add scaling + WidgetPosition::CpuLegend => self.change_cpu_table_position(-1), // TODO: [PO?] Temporary, may change if we add scaling _ => {} } self.app_scroll_positions.scroll_direction = ScrollDirection::UP; @@ -1326,7 +1384,7 @@ impl App { WidgetPosition::Process => self.change_process_position(1), WidgetPosition::Temp => self.change_temp_position(1), WidgetPosition::Disk => self.change_disk_position(1), - WidgetPosition::Cpu => self.change_cpu_table_position(1), // TODO: [PO?] Temporary, may change if we add scaling + WidgetPosition::CpuLegend => self.change_cpu_table_position(1), // TODO: [PO?] Temporary, may change if we add scaling _ => {} } self.app_scroll_positions.scroll_direction = ScrollDirection::DOWN; @@ -1395,4 +1453,24 @@ impl App { .current_scroll_position = (current_posn as i64 + num_to_change_by) as u64; } } + + pub fn handle_scroll_up(&mut self) { + if self.current_widget_selected.is_widget_graph() { + self.zoom_in(); + } else if self.current_widget_selected.is_widget_table() { + self.decrement_position_count(); + } + } + + pub fn handle_scroll_down(&mut self) { + if self.current_widget_selected.is_widget_graph() { + self.zoom_out(); + } else if self.current_widget_selected.is_widget_table() { + self.increment_position_count(); + } + } + + fn zoom_out(&mut self) {} + + fn zoom_in(&mut self) {} } diff --git a/src/canvas.rs b/src/canvas.rs index 80c1dd66..468ac70a 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -235,7 +235,7 @@ impl Painter { .constraints([Constraint::Percentage(100)].as_ref()) .split(f.size()); match &app_state.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::BasicCpu => { + WidgetPosition::Cpu | WidgetPosition::BasicCpu | WidgetPosition::CpuLegend => { let cpu_chunk = Layout::default() .direction(Direction::Horizontal) .margin(0) @@ -272,7 +272,9 @@ impl Painter { WidgetPosition::Temp => { self.draw_temp_table(&mut f, app_state, rect[0], true); } - WidgetPosition::Network | WidgetPosition::BasicNet => { + WidgetPosition::Network + | WidgetPosition::BasicNet + | WidgetPosition::NetworkLegend => { self.draw_network_graph(&mut f, &app_state, rect[0]); } WidgetPosition::Process | WidgetPosition::ProcessSearch => { diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 6a51595e..e9f4ddcf 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -17,7 +17,7 @@ use tui::{ widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, }; -const CPU_SELECT_LEGEND_HEADER: [&str; 2] = ["CPU", "Show (Space)"]; +const CPU_SELECT_LEGEND_HEADER: [&str; 2] = ["CPU", "Show"]; const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"]; lazy_static! { static ref CPU_LEGEND_HEADER_LENS: Vec = CPU_LEGEND_HEADER @@ -92,22 +92,22 @@ impl CpuGraphWidget for Painter { " 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 { - self.colours.highlighted_border_style + border_style } else { self.colours.widget_title_style }) .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Cpu | WidgetPosition::BasicCpu => { - self.colours.highlighted_border_style - } - _ => self.colours.border_style, - }), + .border_style(border_style), ) .x_axis(x_axis) .y_axis(y_axis) @@ -167,7 +167,7 @@ impl CpuGraphWidget for Painter { Row::StyledData( cpu_string_row.iter(), match app_state.current_widget_selected { - WidgetPosition::Cpu => { + WidgetPosition::CpuLegend => { if itx as u64 == app_state .app_scroll_positions @@ -225,6 +225,11 @@ impl CpuGraphWidget for Painter { "".to_string() }; + 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 { @@ -238,19 +243,9 @@ impl CpuGraphWidget for Painter { .block( Block::default() .title(&title) - .title_style(if app_state.is_expanded { - self.colours.highlighted_border_style - } else { - match app_state.current_widget_selected { - WidgetPosition::Cpu => self.colours.highlighted_border_style, - _ => self.colours.border_style, - } - }) + .title_style(title_and_border_style) .borders(Borders::ALL) - .border_style(match app_state.current_widget_selected { - WidgetPosition::Cpu => self.colours.highlighted_border_style, - _ => self.colours.border_style, - }), + .border_style(title_and_border_style), ) .header_style(self.colours.table_header_style) .widths( diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index 77711ba5..f16f9e9e 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -79,9 +79,7 @@ impl MemGraphWidget for Painter { }) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - WidgetPosition::Mem | WidgetPosition::BasicMem => { - self.colours.highlighted_border_style - } + WidgetPosition::Mem => self.colours.highlighted_border_style, _ => self.colours.border_style, }), ) diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index b753ab76..50c829a7 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -71,9 +71,7 @@ impl NetworkGraphWidget for Painter { }) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - WidgetPosition::Network | WidgetPosition::BasicNet => { - self.colours.highlighted_border_style - } + WidgetPosition::Network => self.colours.highlighted_border_style, _ => self.colours.border_style, }), ) diff --git a/src/main.rs b/src/main.rs index f763e1ef..031a1d57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -268,8 +268,8 @@ fn main() -> error::Result<()> { fn handle_mouse_event(event: MouseEvent, app: &mut App) { match event { - MouseEvent::ScrollUp(_x, _y, _modifiers) => app.decrement_position_count(), - MouseEvent::ScrollDown(_x, _y, _modifiers) => app.increment_position_count(), + MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(), + MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(), _ => {} }; } From 3026fbd1bc6ec3d757dba6ad30428812b63190c9 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 8 Mar 2020 19:47:10 -0400 Subject: [PATCH 2/9] Add time scaling. --- src/app.rs | 122 +++++++++++++++++++++++----- src/app/data_farmer.rs | 6 ++ src/canvas.rs | 4 +- src/canvas/widgets/cpu_graph.rs | 13 ++- src/canvas/widgets/mem_graph.rs | 11 ++- src/canvas/widgets/network_graph.rs | 12 ++- src/constants.rs | 9 +- src/data_conversion.rs | 121 +++++++++++++++++++-------- src/main.rs | 73 +++++++++++++++-- 9 files changed, 299 insertions(+), 72 deletions(-) diff --git a/src/app.rs b/src/app.rs index 4d536262..8e11f1fa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -227,6 +227,8 @@ pub struct NetworkState { pub is_showing_rx: bool, pub is_showing_tx: bool, pub zoom_level: f64, + pub display_time: u128, + pub force_update: bool, } impl Default for NetworkState { @@ -236,6 +238,8 @@ impl Default for NetworkState { is_showing_rx: true, is_showing_tx: true, zoom_level: 100.0, + display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + force_update: false, } } } @@ -245,6 +249,8 @@ pub struct CpuState { pub is_showing_tray: bool, pub zoom_level: f64, pub core_show_vec: Vec, + pub display_time: u128, + pub force_update: bool, } impl Default for CpuState { @@ -253,6 +259,8 @@ impl Default for CpuState { is_showing_tray: false, zoom_level: 100.0, core_show_vec: Vec::new(), + display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + force_update: false, } } } @@ -263,6 +271,8 @@ pub struct MemState { pub is_showing_ram: bool, pub is_showing_swap: bool, pub zoom_level: f64, + pub display_time: u128, + pub force_update: bool, } impl Default for MemState { @@ -272,6 +282,8 @@ impl Default for MemState { is_showing_ram: true, is_showing_swap: true, zoom_level: 100.0, + display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + force_update: false, } } } @@ -279,7 +291,7 @@ impl Default for MemState { pub struct App { pub process_sorting_type: processes::ProcessSorting, pub process_sorting_reverse: bool, - pub update_process_gui: bool, + pub force_update_processes: bool, pub app_scroll_positions: AppScrollState, pub current_widget_selected: WidgetPosition, pub previous_basic_table_selected: WidgetPosition, @@ -305,6 +317,7 @@ pub struct App { impl App { #[allow(clippy::too_many_arguments)] + // TODO: [REFACTOR] use builder pattern instead. pub fn new( show_average_cpu: bool, temperature_type: temperature::TemperatureType, update_rate_in_milliseconds: u128, use_dot: bool, left_legend: bool, @@ -314,7 +327,7 @@ impl App { App { process_sorting_type: processes::ProcessSorting::CPU, process_sorting_reverse: true, - update_process_gui: false, + force_update_processes: false, current_widget_selected: if use_basic_mode { match current_widget_selected { WidgetPosition::Cpu => WidgetPosition::BasicCpu, @@ -443,7 +456,7 @@ impl App { if !self.is_in_dialog() { if let WidgetPosition::Process = self.current_widget_selected { self.enable_grouping = !(self.enable_grouping); - self.update_process_gui = true; + self.force_update_processes = true; } } } @@ -455,7 +468,7 @@ impl App { if self.is_grouped() { self.search_with_name(); } else { - self.update_process_gui = true; + self.force_update_processes = true; } } WidgetPosition::ProcessSearch => { @@ -539,14 +552,14 @@ impl App { 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.update_process_gui = 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.update_process_gui = true; + self.force_update_processes = true; } } @@ -557,19 +570,19 @@ impl App { pub fn toggle_ignore_case(&mut self) { self.process_search_state.search_toggle_ignore_case(); self.update_regex(); - self.update_process_gui = true; + 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.update_process_gui = true; + self.force_update_processes = true; } pub fn toggle_search_regex(&mut self) { self.process_search_state.search_toggle_regex(); self.update_regex(); - self.update_process_gui = true; + self.force_update_processes = true; } pub fn update_regex(&mut self) { @@ -703,7 +716,7 @@ impl App { ); self.update_regex(); - self.update_process_gui = true; + self.force_update_processes = true; } } _ => {} @@ -720,7 +733,7 @@ impl App { pub fn clear_search(&mut self) { if let WidgetPosition::ProcessSearch = self.current_widget_selected { - self.update_process_gui = true; + self.force_update_processes = true; self.process_search_state.search_state = AppSearchState::reset(); } } @@ -772,7 +785,7 @@ impl App { self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT; self.update_regex(); - self.update_process_gui = true; + self.force_update_processes = true; } } } @@ -967,7 +980,7 @@ impl App { UnicodeWidthChar::width(caught_char).unwrap_or(0); self.update_regex(); - self.update_process_gui = true; + self.force_update_processes = true; self.process_search_state.search_state.cursor_direction = CursorDirection::RIGHT; } @@ -1016,6 +1029,9 @@ impl App { '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 { @@ -1027,7 +1043,7 @@ impl App { self.process_sorting_reverse = true; } } - self.update_process_gui = true; + self.force_update_processes = true; self.app_scroll_positions .process_scroll_state .current_scroll_position = 0; @@ -1042,7 +1058,7 @@ impl App { self.process_sorting_reverse = true; } } - self.update_process_gui = true; + self.force_update_processes = true; self.app_scroll_positions .process_scroll_state .current_scroll_position = 0; @@ -1059,7 +1075,7 @@ impl App { self.process_sorting_reverse = false; } } - self.update_process_gui = true; + self.force_update_processes = true; self.app_scroll_positions .process_scroll_state .current_scroll_position = 0; @@ -1075,7 +1091,7 @@ impl App { self.process_sorting_reverse = false; } } - self.update_process_gui = true; + self.force_update_processes = true; self.app_scroll_positions .process_scroll_state .current_scroll_position = 0; @@ -1088,9 +1104,9 @@ impl App { 'K' => self.move_widget_selection_up(), 'J' => self.move_widget_selection_down(), ' ' => self.on_space(), - '+' => {} - '-' => {} - '=' => {} + '+' => self.zoom_in(), + '-' => self.zoom_out(), + '=' => self.reset_zoom(), _ => {} } @@ -1470,7 +1486,69 @@ impl App { } } - fn zoom_out(&mut self) {} + fn zoom_out(&mut self) { + match self.current_widget_selected { + WidgetPosition::Cpu => { + if self.cpu_state.display_time < constants::STALE_MAX_MILLISECONDS { + self.cpu_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + self.cpu_state.force_update = true; + } + } + WidgetPosition::Mem => { + if self.mem_state.display_time < constants::STALE_MAX_MILLISECONDS { + self.mem_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + self.mem_state.force_update = true; + } + } + WidgetPosition::Network => { + if self.net_state.display_time < constants::STALE_MAX_MILLISECONDS { + self.net_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + self.net_state.force_update = true; + } + } + _ => {} + } + } - fn zoom_in(&mut self) {} + fn zoom_in(&mut self) { + match self.current_widget_selected { + WidgetPosition::Cpu => { + if self.cpu_state.display_time > constants::STALE_MIN_MILLISECONDS { + self.cpu_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + self.cpu_state.force_update = true; + } + } + WidgetPosition::Mem => { + if self.mem_state.display_time > constants::STALE_MIN_MILLISECONDS { + self.mem_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + self.mem_state.force_update = true; + } + } + WidgetPosition::Network => { + if self.net_state.display_time > constants::STALE_MIN_MILLISECONDS { + self.net_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + self.net_state.force_update = true; + } + } + _ => {} + } + } + + fn reset_zoom(&mut self) { + match self.current_widget_selected { + WidgetPosition::Cpu => { + self.cpu_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.cpu_state.force_update = true; + } + WidgetPosition::Mem => { + self.mem_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.mem_state.force_update = true; + } + WidgetPosition::Network => { + self.net_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.net_state.force_update = true; + } + _ => {} + } + } } diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index e8e98160..d3d77ca2 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -45,6 +45,7 @@ pub struct TimedData { #[derive(Debug)] pub struct DataCollection { pub current_instant: Instant, + pub frozen_instant: Option, pub timed_data_vec: Vec<(Instant, TimedData)>, pub network_harvest: network::NetworkHarvest, pub memory_harvest: mem::MemHarvest, @@ -62,6 +63,7 @@ impl Default for DataCollection { fn default() -> Self { DataCollection { current_instant: Instant::now(), + frozen_instant: None, timed_data_vec: Vec::default(), network_harvest: network::NetworkHarvest::default(), memory_harvest: mem::MemHarvest::default(), @@ -78,6 +80,10 @@ impl Default for DataCollection { } impl DataCollection { + pub fn set_frozen_time(&mut self) { + self.frozen_instant = Some(self.current_instant); + } + pub fn clean_data(&mut self, max_time_millis: u128) { let current_time = Instant::now(); diff --git a/src/canvas.rs b/src/canvas.rs index 468ac70a..aa657ab2 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -35,12 +35,12 @@ pub struct DisplayableData { pub network_data_tx: Vec<(f64, f64)>, pub disk_data: Vec>, pub temp_sensor_data: Vec>, + // Not the final value pub process_data: HashMap, // Not the final value pub grouped_process_data: Vec, - // Not the final value - pub finalized_process_data: Vec, // What's actually displayed + pub finalized_process_data: Vec, pub mem_label: String, pub swap_label: String, pub mem_data: Vec<(f64, f64)>, diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index e9f4ddcf..59cee390 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -41,8 +41,17 @@ impl CpuGraphWidget for Painter { fn draw_cpu_graph(&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect) { let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; - // CPU usage graph - let x_axis: Axis<'_, String> = Axis::default().bounds([0.0, TIME_STARTS_FROM as f64]); + let display_time_labels = [ + format!("{}s", app_state.cpu_state.display_time / 1000), + "0s".to_string(), + ]; + let x_axis = Axis::default() + .bounds([0.0, app_state.cpu_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels); + + // 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) diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index f16f9e9e..58ca8545 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -3,7 +3,6 @@ use std::cmp::max; use crate::{ app::{App, WidgetPosition}, canvas::Painter, - constants::*, }; use tui::{ @@ -22,7 +21,15 @@ impl MemGraphWidget for Painter { let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data; let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; - let x_axis: Axis<'_, String> = Axis::default().bounds([0.0, TIME_STARTS_FROM as f64]); + let display_time_labels = [ + format!("{}s", app_state.mem_state.display_time / 1000), + "0s".to_string(), + ]; + let x_axis = Axis::default() + .bounds([0.0, app_state.mem_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels); // Offset as the zero value isn't drawn otherwise... let y_axis: Axis<'_, &str> = Axis::default() diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index 50c829a7..c22f3847 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -37,7 +37,17 @@ impl NetworkGraphWidget for Painter { 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 x_axis: Axis<'_, String> = Axis::default().bounds([0.0, 60_000.0]); + let display_time_labels = [ + format!("{}s", app_state.net_state.display_time / 1000), + "0s".to_string(), + ]; + let x_axis = Axis::default() + .bounds([0.0, app_state.net_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels); + + // 0 is offset. let y_axis: Axis<'_, &str> = Axis::default() .style(self.colours.graph_style) .labels_style(self.colours.graph_style) diff --git a/src/constants.rs b/src/constants.rs index c8f1c068..b177d141 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,6 +1,11 @@ // How long to store data. -pub const STALE_MAX_MILLISECONDS: u128 = 60 * 1000; -pub const TIME_STARTS_FROM: u64 = 60 * 1000; +pub const STALE_MAX_MILLISECONDS: u128 = 300 * 1000; // Keep 5 minutes of data. + +// How much data is SHOWN +pub const DEFAULT_DISPLAY_MILLISECONDS: u128 = 60 * 1000; // Defaults to 1 min. +pub const STALE_MIN_MILLISECONDS: u128 = 30 * 1000; // Lowest is 30 seconds +pub const TIME_CHANGE_MILLISECONDS: u128 = 15 * 1000; // How much to increment each time + pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; // How fast the screen refreshes pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u128 = 1000; diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 9191e378..341ada48 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -3,22 +3,21 @@ use std::collections::HashMap; -use constants::*; - use crate::{ app::{ data_farmer, data_harvester::{self, processes::ProcessHarvest}, App, }, - constants, utils::gen_util::{get_exact_byte_values, get_simple_byte_values}, }; +type Point = (f64, f64); + #[derive(Default, Debug)] pub struct ConvertedNetworkData { - pub rx: Vec<(f64, f64)>, - pub tx: Vec<(f64, f64)>, + pub rx: Vec, + pub tx: Vec, pub rx_display: String, pub tx_display: String, pub total_rx_display: String, @@ -38,7 +37,7 @@ pub struct ConvertedProcessData { pub struct ConvertedCpuData { pub cpu_name: String, /// Tuple is time, value - pub cpu_data: Vec<(f64, f64)>, + pub cpu_data: Vec, } pub fn convert_temp_row(app: &App) -> Vec> { @@ -103,16 +102,24 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec Vec { let mut cpu_data_vector: Vec = Vec::new(); - let current_time = current_data.current_instant; + let current_time = if is_frozen { + if let Some(frozen_instant) = current_data.frozen_instant { + frozen_instant + } else { + current_data.current_instant + } + } else { + current_data.current_instant + }; let cpu_listing_offset = if show_avg_cpu { 0 } else { 1 }; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = (TIME_STARTS_FROM as f64 - - current_time.duration_since(*time).as_millis() as f64) - .floor(); + let time_from_start: f64 = + (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); for (itx, cpu) in data.cpu_data.iter().enumerate() { if !show_avg_cpu && itx == 0 { @@ -139,19 +146,32 @@ pub fn convert_cpu_data_points( .cpu_data .push((time_from_start, cpu.0)); } + + if *time == current_time { + break; + } } cpu_data_vector } -pub fn convert_mem_data_points(current_data: &data_farmer::DataCollection) -> Vec<(f64, f64)> { - let mut result: Vec<(f64, f64)> = Vec::new(); - let current_time = current_data.current_instant; +pub fn convert_mem_data_points( + current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, +) -> Vec { + let mut result: Vec = Vec::new(); + let current_time = if is_frozen { + if let Some(frozen_instant) = current_data.frozen_instant { + frozen_instant + } else { + current_data.current_instant + } + } else { + current_data.current_instant + }; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = (TIME_STARTS_FROM as f64 - - current_time.duration_since(*time).as_millis() as f64) - .floor(); + let time_from_start: f64 = + (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); //Insert joiner points for &(joiner_offset, joiner_val) in &data.mem_data.1 { @@ -160,19 +180,32 @@ pub fn convert_mem_data_points(current_data: &data_farmer::DataCollection) -> Ve } result.push((time_from_start, data.mem_data.0)); + + if *time == current_time { + break; + } } result } -pub fn convert_swap_data_points(current_data: &data_farmer::DataCollection) -> Vec<(f64, f64)> { - let mut result: Vec<(f64, f64)> = Vec::new(); - let current_time = current_data.current_instant; +pub fn convert_swap_data_points( + current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, +) -> Vec { + let mut result: Vec = Vec::new(); + let current_time = if is_frozen { + if let Some(frozen_instant) = current_data.frozen_instant { + frozen_instant + } else { + current_data.current_instant + } + } else { + current_data.current_instant + }; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = (TIME_STARTS_FROM as f64 - - current_time.duration_since(*time).as_millis() as f64) - .floor(); + let time_from_start: f64 = + (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); //Insert joiner points for &(joiner_offset, joiner_val) in &data.swap_data.1 { @@ -181,6 +214,10 @@ pub fn convert_swap_data_points(current_data: &data_farmer::DataCollection) -> V } result.push((time_from_start, data.swap_data.0)); + + if *time == current_time { + break; + } } result @@ -222,17 +259,25 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String (mem_label, swap_label) } -pub fn convert_network_data_points( - current_data: &data_farmer::DataCollection, -) -> ConvertedNetworkData { - let mut rx: Vec<(f64, f64)> = Vec::new(); - let mut tx: Vec<(f64, f64)> = Vec::new(); +pub fn get_rx_tx_data_points( + current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, +) -> (Vec, Vec) { + let mut rx: Vec = Vec::new(); + let mut tx: Vec = Vec::new(); + + let current_time = if is_frozen { + if let Some(frozen_instant) = current_data.frozen_instant { + frozen_instant + } else { + current_data.current_instant + } + } else { + current_data.current_instant + }; - let current_time = current_data.current_instant; for (time, data) in ¤t_data.timed_data_vec { - let time_from_start: f64 = (TIME_STARTS_FROM as f64 - - current_time.duration_since(*time).as_millis() as f64) - .floor(); + let time_from_start: f64 = + (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); //Insert joiner points for &(joiner_offset, joiner_val) in &data.rx_data.1 { @@ -247,8 +292,20 @@ pub fn convert_network_data_points( rx.push((time_from_start, data.rx_data.0)); tx.push((time_from_start, data.tx_data.0)); + + if *time == current_time { + break; + } } + (rx, tx) +} + +pub fn convert_network_data_points( + current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, +) -> ConvertedNetworkData { + let (rx, tx) = get_rx_tx_data_points(current_data, display_time, is_frozen); + let total_rx_converted_result: (f64, String); let rx_converted_result: (f64, String); let total_tx_converted_result: (f64, String); diff --git a/src/main.rs b/src/main.rs index 031a1d57..adf12e7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -187,13 +187,12 @@ fn main() -> error::Result<()> { if handle_key_event_or_break(event, &mut app, &rtx) { break; } - - if app.update_process_gui { - update_final_process_list(&mut app); - app.update_process_gui = false; - } + handle_force_redraws(&mut app); + } + BottomEvent::MouseInput(event) => { + handle_mouse_event(event, &mut app); + handle_force_redraws(&mut app); } - BottomEvent::MouseInput(event) => handle_mouse_event(event, &mut app), BottomEvent::Update(data) => { app.data_collection.eat_data(&data); @@ -201,7 +200,11 @@ fn main() -> error::Result<()> { // Convert all data into tui-compliant components // Network - let network_data = convert_network_data_points(&app.data_collection); + let network_data = convert_network_data_points( + &app.data_collection, + app.net_state.display_time, + 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; @@ -215,8 +218,16 @@ 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.canvas_data.swap_data = convert_swap_data_points(&app.data_collection); + app.canvas_data.mem_data = convert_mem_data_points( + &app.data_collection, + app.mem_state.display_time, + false, + ); + app.canvas_data.swap_data = convert_swap_data_points( + &app.data_collection, + app.mem_state.display_time, + 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; @@ -225,6 +236,8 @@ fn main() -> error::Result<()> { app.canvas_data.cpu_data = convert_cpu_data_points( app.app_config_fields.show_average_cpu, &app.data_collection, + app.cpu_state.display_time, + false, ); // Pre-fill CPU if needed @@ -560,6 +573,48 @@ fn panic_hook(panic_info: &PanicInfo<'_>) { .unwrap(); } +fn handle_force_redraws(app: &mut App) { + if app.force_update_processes { + update_final_process_list(app); + app.force_update_processes = false; + } + + if app.cpu_state.force_update { + app.canvas_data.cpu_data = convert_cpu_data_points( + app.app_config_fields.show_average_cpu, + &app.data_collection, + app.cpu_state.display_time, + app.is_frozen, + ); + app.cpu_state.force_update = false; + } + + if app.mem_state.force_update { + app.canvas_data.mem_data = convert_mem_data_points( + &app.data_collection, + app.mem_state.display_time, + app.is_frozen, + ); + app.canvas_data.swap_data = convert_swap_data_points( + &app.data_collection, + app.mem_state.display_time, + app.is_frozen, + ); + app.mem_state.force_update = false; + } + + if app.net_state.force_update { + let (rx, tx) = get_rx_tx_data_points( + &app.data_collection, + app.net_state.display_time, + app.is_frozen, + ); + app.canvas_data.network_data_rx = rx; + app.canvas_data.network_data_tx = tx; + app.net_state.force_update = false; + } +} + fn update_final_process_list(app: &mut App) { let mut filtered_process_data: Vec = if app.is_grouped() { app.canvas_data From f70cf02414a5263c000f06c1b36ee19e11bf23a5 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 8 Mar 2020 21:56:30 -0400 Subject: [PATCH 3/9] Add configurable default time and interval values Also added documentation both in app and in the README. --- README.md | 6 +++- src/app.rs | 78 ++++++++++++++++++++++++++--------------- src/app/data_farmer.rs | 4 +-- src/constants.rs | 43 ++++++++++++++++++----- src/data_conversion.rs | 11 +++--- src/main.rs | 12 +++++-- src/options.rs | 70 ++++++++++++++++++++++++++++++++---- tests/arg_rate_tests.rs | 6 ++-- 8 files changed, 174 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index a0cf651d..4dda210b 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Run using `btm`. - `-v`, `--version` displays the version number and exits. -- `-r `, `--rate ` will set the refresh rate in _milliseconds_. Lowest it can go is 250ms, the highest it can go is 2128 - 1. Defaults to 1000ms, and lower values may take more resources due to more frequent polling of data, and may be less accurate in some circumstances. +- `-r `, `--rate ` will set the refresh rate in _milliseconds_. Lowest it can go is 250ms, the highest it can go is 264 - 1. Defaults to 1000ms, and lower values may take more resources due to more frequent polling of data, and may be less accurate in some circumstances. - `-l`, `--left_legend` will move external table legends to the left side rather than the right side. Right side is default. @@ -140,6 +140,10 @@ Run using `btm`. - `-b`, `--basic` will enable basic mode, removing all graphs from the main interface and condensing data. +- `-t`, `--default_time_value` will set the default time interval graphs will display to (in milliseconds). Lowest is 30 seconds, defaults to 60 seconds. + +- `-i`, `--time_delta` will set the amount each zoom in/out action will change the time interval of a graph (in milliseconds). Lowest is 1 second, defaults to 15 seconds. + ### Keybindings #### General diff --git a/src/app.rs b/src/app.rs index 8e11f1fa..1b1cffa2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -209,9 +209,10 @@ impl Default for AppHelpDialogState { } /// AppConfigFields is meant to cover basic fields that would normally be set -/// by config files or launch options. Don't need to be mutable (set and forget). +/// by config files or launch options. +#[derive(Default)] pub struct AppConfigFields { - pub update_rate_in_milliseconds: u128, + pub update_rate_in_milliseconds: u64, pub temperature_type: temperature::TemperatureType, pub use_dot: bool, pub left_legend: bool, @@ -219,6 +220,8 @@ pub struct AppConfigFields { 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, } /// Network specific @@ -227,7 +230,7 @@ pub struct NetworkState { pub is_showing_rx: bool, pub is_showing_tx: bool, pub zoom_level: f64, - pub display_time: u128, + pub display_time: u64, pub force_update: bool, } @@ -238,7 +241,7 @@ impl Default for NetworkState { is_showing_rx: true, is_showing_tx: true, zoom_level: 100.0, - display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, } } @@ -249,7 +252,7 @@ pub struct CpuState { pub is_showing_tray: bool, pub zoom_level: f64, pub core_show_vec: Vec, - pub display_time: u128, + pub display_time: u64, pub force_update: bool, } @@ -259,7 +262,7 @@ impl Default for CpuState { is_showing_tray: false, zoom_level: 100.0, core_show_vec: Vec::new(), - display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, } } @@ -271,7 +274,7 @@ pub struct MemState { pub is_showing_ram: bool, pub is_showing_swap: bool, pub zoom_level: f64, - pub display_time: u128, + pub display_time: u64, pub force_update: bool, } @@ -282,7 +285,7 @@ impl Default for MemState { is_showing_ram: true, is_showing_swap: true, zoom_level: 100.0, - display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, } } @@ -320,10 +323,19 @@ impl App { // TODO: [REFACTOR] use builder pattern instead. pub fn new( show_average_cpu: bool, temperature_type: temperature::TemperatureType, - update_rate_in_milliseconds: u128, use_dot: bool, left_legend: bool, + update_rate_in_milliseconds: u64, use_dot: bool, left_legend: bool, use_current_cpu_total: bool, current_widget_selected: WidgetPosition, - show_disabled_data: bool, use_basic_mode: bool, + show_disabled_data: bool, use_basic_mode: bool, default_time_value: u64, + time_interval: u64, ) -> App { + let mut cpu_state = CpuState::default(); + let mut mem_state = MemState::default(); + let mut net_state = NetworkState::default(); + + cpu_state.display_time = default_time_value; + mem_state.display_time = default_time_value; + net_state.display_time = default_time_value; + App { process_sorting_type: processes::ProcessSorting::CPU, process_sorting_reverse: true, @@ -365,12 +377,14 @@ impl App { use_current_cpu_total, show_disabled_data, use_basic_mode, + default_time_value, + time_interval, }, is_expanded: false, is_resized: false, - cpu_state: CpuState::default(), - mem_state: MemState::default(), - net_state: NetworkState::default(), + cpu_state, + mem_state, + net_state, } } @@ -947,7 +961,7 @@ impl App { if current_key_press_inst .duration_since(self.last_key_press) .as_millis() - > constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS + > constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS as u128 { self.reset_multi_tap_keys(); } @@ -1489,20 +1503,23 @@ impl App { fn zoom_out(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - if self.cpu_state.display_time < constants::STALE_MAX_MILLISECONDS { - self.cpu_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.cpu_state.display_time + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + self.cpu_state.display_time = new_time; self.cpu_state.force_update = true; } } WidgetPosition::Mem => { - if self.mem_state.display_time < constants::STALE_MAX_MILLISECONDS { - self.mem_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.mem_state.display_time + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + self.mem_state.display_time = new_time; self.mem_state.force_update = true; } } WidgetPosition::Network => { - if self.net_state.display_time < constants::STALE_MAX_MILLISECONDS { - self.net_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.net_state.display_time + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + self.net_state.display_time = new_time; self.net_state.force_update = true; } } @@ -1513,20 +1530,23 @@ impl App { fn zoom_in(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - if self.cpu_state.display_time > constants::STALE_MIN_MILLISECONDS { - self.cpu_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.cpu_state.display_time - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + self.cpu_state.display_time = new_time; self.cpu_state.force_update = true; } } WidgetPosition::Mem => { - if self.mem_state.display_time > constants::STALE_MIN_MILLISECONDS { - self.mem_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.mem_state.display_time - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + self.mem_state.display_time = new_time; self.mem_state.force_update = true; } } WidgetPosition::Network => { - if self.net_state.display_time > constants::STALE_MIN_MILLISECONDS { - self.net_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.net_state.display_time - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + self.net_state.display_time = new_time; self.net_state.force_update = true; } } @@ -1537,15 +1557,15 @@ impl App { fn reset_zoom(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - self.cpu_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.cpu_state.display_time = self.app_config_fields.default_time_value; self.cpu_state.force_update = true; } WidgetPosition::Mem => { - self.mem_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.mem_state.display_time = self.app_config_fields.default_time_value; self.mem_state.force_update = true; } WidgetPosition::Network => { - self.net_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.net_state.display_time = self.app_config_fields.default_time_value; self.net_state.force_update = true; } _ => {} diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index d3d77ca2..5fa766a5 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -84,12 +84,12 @@ impl DataCollection { self.frozen_instant = Some(self.current_instant); } - pub fn clean_data(&mut self, max_time_millis: u128) { + pub fn clean_data(&mut self, max_time_millis: u64) { let current_time = Instant::now(); let mut remove_index = 0; for entry in &self.timed_data_vec { - if current_time.duration_since(entry.0).as_millis() >= max_time_millis { + if current_time.duration_since(entry.0).as_millis() >= max_time_millis as u128 { remove_index += 1; } else { break; diff --git a/src/constants.rs b/src/constants.rs index b177d141..f488ccfe 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,15 +1,15 @@ // How long to store data. -pub const STALE_MAX_MILLISECONDS: u128 = 300 * 1000; // Keep 5 minutes of data. +pub const STALE_MAX_MILLISECONDS: u64 = 300 * 1000; // Keep 5 minutes of data. // How much data is SHOWN -pub const DEFAULT_DISPLAY_MILLISECONDS: u128 = 60 * 1000; // Defaults to 1 min. -pub const STALE_MIN_MILLISECONDS: u128 = 30 * 1000; // Lowest is 30 seconds -pub const TIME_CHANGE_MILLISECONDS: u128 = 15 * 1000; // How much to increment each time +pub const DEFAULT_TIME_MILLISECONDS: u64 = 60 * 1000; // Defaults to 1 min. +pub const STALE_MIN_MILLISECONDS: u64 = 30 * 1000; // Lowest is 30 seconds +pub const TIME_CHANGE_MILLISECONDS: u64 = 15 * 1000; // How much to increment each time pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; // How fast the screen refreshes -pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u128 = 1000; -pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u128 = 1000; +pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000; +pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000; // Number of colours to generate for the CPU chart/table pub const NUM_COLOURS: i32 = 256; @@ -30,7 +30,7 @@ lazy_static! { } // Help text -pub const GENERAL_HELP_TEXT: [&str; 15] = [ +pub const GENERAL_HELP_TEXT: [&str; 18] = [ "General Keybindings\n\n", "q, Ctrl-c Quit bottom\n", "Esc Close filters, dialog boxes, etc.\n", @@ -46,6 +46,9 @@ pub const GENERAL_HELP_TEXT: [&str; 15] = [ "G Skip to the last entry of a list\n", "Enter Maximize the currently selected widget\n", "/ Filter out graph lines (only CPU at the moment)\n", + "+ Zoom in (decrease time range)\n", + "- Zoom out (increase time range)\n", + "= Reset zoom\n", ]; pub const PROCESS_HELP_TEXT: [&str; 8] = [ @@ -90,15 +93,34 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" # is also set here. [flags] +# Whether to display an average cpu entry. #avg_cpu = true + +# Whether to use dot markers rather than braille. #dot_marker = false + +# The update rate of the application. #rate = 1000 + +# Whether to put the CPU legend to the left. #left_legend = false + +# Whether to set CPU% on a process to be based on the total CPU or just current usage. #current_usage = false + +# Whether to group processes with the same name together by default. #group_processes = false + +# Whether to make process searching case sensitive by default. #case_sensitive = false + +# Whether to make process searching look for matching the entire word by default. #whole_word = true + +# Whether to make process searching use regex by default. #regex = true + +# Whether to show CPU entries in the legend when they are hidden. #show_disabled_data = true # Defaults to Celsius. Temperature is one of: @@ -117,6 +139,11 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" #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 # 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 @@ -132,7 +159,7 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" # Represents the colour of the label each widget has. #widget_title_color="#cc241d" -# Represents the average CPU color +# Represents the average CPU color. #avg_cpu_color="#d3869b" # Represents the colour the core will use in the CPU legend and graph. diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 341ada48..c99bf75f 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -102,7 +102,7 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec Vec { let mut cpu_data_vector: Vec = Vec::new(); @@ -156,7 +156,7 @@ pub fn convert_cpu_data_points( } pub fn convert_mem_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> Vec { let mut result: Vec = Vec::new(); let current_time = if is_frozen { @@ -190,7 +190,7 @@ pub fn convert_mem_data_points( } pub fn convert_swap_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> Vec { let mut result: Vec = Vec::new(); let current_time = if is_frozen { @@ -260,7 +260,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: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> (Vec, Vec) { let mut rx: Vec = Vec::new(); let mut tx: Vec = Vec::new(); @@ -275,6 +275,7 @@ pub fn get_rx_tx_data_points( current_data.current_instant }; + // TODO: [REFACTOR] Can we use combine 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(); @@ -302,7 +303,7 @@ pub fn get_rx_tx_data_points( } pub fn convert_network_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> ConvertedNetworkData { let (rx, tx) = get_rx_tx_data_points(current_data, display_time, is_frozen); diff --git a/src/main.rs b/src/main.rs index adf12e7d..9d09c262 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,7 +85,9 @@ fn get_matches() -> clap::ArgMatches<'static> { (@arg CASE_SENSITIVE: -S --case_sensitive "Match case when searching by default.") (@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.") (@arg REGEX_DEFAULT: -R --regex "Use regex in searching by default.") - (@arg SHOW_DISABLED_DATA: -s --show_disabled_data "Show disabled data entries.") + (@arg SHOW_DISABLED_DATA: -s --show_disabled_data "Show disabled data entries.") + (@arg DEFAULT_TIME_VALUE: -t --default_time_value +takes_value "Default time value for graphs in milliseconds; minimum is 30s, defaults to 60s.") + (@arg TIME_DELTA: -i --time_delta +takes_value "The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s.") (@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.") @@ -105,7 +107,7 @@ fn main() -> error::Result<()> { let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; - let update_rate_in_milliseconds: u128 = + let update_rate_in_milliseconds: u64 = get_update_rate_in_milliseconds(&matches.value_of("RATE_MILLIS"), &config)?; // Set other settings @@ -117,6 +119,8 @@ fn main() -> error::Result<()> { let current_widget_selected = get_default_widget(&matches, &config); let show_disabled_data = get_show_disabled_data_option(&matches, &config); let use_basic_mode = get_use_basic_mode_option(&matches, &config); + let default_time_value = get_default_time_value_option(&matches, &config)?; + let time_interval = get_time_interval_option(&matches, &config)?; // Create "app" struct, which will control most of the program and store settings/state let mut app = App::new( @@ -129,6 +133,8 @@ fn main() -> error::Result<()> { current_widget_selected, show_disabled_data, use_basic_mode, + default_time_value, + time_interval, ); enable_app_grouping(&matches, &config, &mut app); @@ -369,9 +375,9 @@ fn handle_key_event_or_break( app.reset(); } } - KeyCode::Char('u') => app.clear_search(), KeyCode::Char('a') => app.skip_cursor_beginning(), KeyCode::Char('e') => app.skip_cursor_end(), + KeyCode::Char('u') => app.clear_search(), // KeyCode::Char('j') => {}, // Move down // KeyCode::Char('k') => {}, // Move up // KeyCode::Char('h') => {}, // Move right diff --git a/src/options.rs b/src/options.rs index 372165bd..77884e78 100644 --- a/src/options.rs +++ b/src/options.rs @@ -27,6 +27,8 @@ pub struct ConfigFlags { pub default_widget: Option, pub show_disabled_data: Option, pub basic: Option, + pub default_time_value: Option, + pub time_delta: Option, //disabled_cpu_cores: Option>, // TODO: [FEATURE] Enable disabling cores in config/flags } @@ -52,12 +54,12 @@ pub struct ConfigColours { pub fn get_update_rate_in_milliseconds( update_rate: &Option<&str>, config: &Config, -) -> error::Result { +) -> error::Result { let update_rate_in_milliseconds = if let Some(update_rate) = update_rate { - update_rate.parse::()? + update_rate.parse::()? } else if let Some(flags) = &config.flags { if let Some(rate) = flags.rate { - rate as u128 + rate } else { DEFAULT_REFRESH_RATE_IN_MILLISECONDS } @@ -67,11 +69,11 @@ pub fn get_update_rate_in_milliseconds( if update_rate_in_milliseconds < 250 { return Err(BottomError::InvalidArg( - "Please set your update rate to be greater than 250 milliseconds.".to_string(), + "Please set your update rate to be at least 250 milliseconds.".to_string(), )); - } else if update_rate_in_milliseconds > u128::from(std::u64::MAX) { + } else if update_rate_in_milliseconds as u128 > std::u64::MAX as u128 { return Err(BottomError::InvalidArg( - "Please set your update rate to be less than unsigned INT_MAX.".to_string(), + "Please set your update rate to be at most unsigned INT_MAX.".to_string(), )); } @@ -178,6 +180,62 @@ pub fn get_use_basic_mode_option(matches: &clap::ArgMatches<'static>, config: &C false } +pub fn get_default_time_value_option( + matches: &clap::ArgMatches<'static>, config: &Config, +) -> error::Result { + let default_time = if let Some(default_time_value) = matches.value_of("DEFAULT_TIME_VALUE") { + default_time_value.parse::()? + } else if let Some(flags) = &config.flags { + if let Some(default_time_value) = flags.default_time_value { + default_time_value + } else { + DEFAULT_TIME_MILLISECONDS + } + } else { + DEFAULT_TIME_MILLISECONDS + }; + + if default_time < 30000 { + return Err(BottomError::InvalidArg( + "Please set your default value to be at least 30 seconds.".to_string(), + )); + } else if default_time as u128 > std::u64::MAX as u128 { + return Err(BottomError::InvalidArg( + "Please set your default value to be at most unsigned INT_MAX.".to_string(), + )); + } + + Ok(default_time) +} + +pub fn get_time_interval_option( + matches: &clap::ArgMatches<'static>, config: &Config, +) -> error::Result { + let time_interval = if let Some(time_interval) = matches.value_of("TIME_DELTA") { + time_interval.parse::()? + } else if let Some(flags) = &config.flags { + if let Some(time_interval) = flags.time_delta { + time_interval + } else { + TIME_CHANGE_MILLISECONDS + } + } else { + TIME_CHANGE_MILLISECONDS + }; + + if time_interval < 1000 { + return Err(BottomError::InvalidArg( + "Please set your time interval to be at least 1 second.".to_string(), + )); + } else if time_interval as u128 > std::u64::MAX as u128 { + return Err(BottomError::InvalidArg( + "Please set your time interval to be at most unsigned INT_MAX.".to_string(), + )); + } + + Ok(time_interval) +} + pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { if matches.is_present("GROUP_PROCESSES") { app.toggle_grouping(); diff --git a/tests/arg_rate_tests.rs b/tests/arg_rate_tests.rs index 85b3da0f..7f22431c 100644 --- a/tests/arg_rate_tests.rs +++ b/tests/arg_rate_tests.rs @@ -28,7 +28,9 @@ fn test_small_rate() -> Result<(), Box> { .arg("249") .assert() .failure() - .stderr(predicate::str::contains("rate to be greater than 250")); + .stderr(predicate::str::contains( + "Please set your update rate to be at least 250 milliseconds.", + )); Ok(()) } @@ -40,7 +42,7 @@ fn test_large_rate() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "rate to be less than unsigned INT_MAX.", + "Please set your update rate to be at most unsigned INT_MAX.", )); Ok(()) } From 78a05bc68377497dfd52eaee37c7a5e3bedd71b1 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 8 Mar 2020 22:19:57 -0400 Subject: [PATCH 4/9] Fixes bug with too large inputs causing a panic We would prefer a more graceful error message stating what went wrong. Caught by the Travis test. --- README.md | 2 +- src/main.rs | 6 ++- src/options.rs | 36 ++++++++-------- tests/{arg_rate_tests.rs => arg_tests.rs} | 52 +++++++++++++++++++++++ 4 files changed, 75 insertions(+), 21 deletions(-) rename tests/{arg_rate_tests.rs => arg_tests.rs} (66%) diff --git a/README.md b/README.md index 4dda210b..35dd41b4 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Run using `btm`. - `-t`, `--default_time_value` will set the default time interval graphs will display to (in milliseconds). Lowest is 30 seconds, defaults to 60 seconds. -- `-i`, `--time_delta` will set the amount each zoom in/out action will change the time interval of a graph (in milliseconds). Lowest is 1 second, defaults to 15 seconds. +- `-d`, `--time_delta` will set the amount each zoom in/out action will change the time interval of a graph (in milliseconds). Lowest is 1 second, defaults to 15 seconds. ### Keybindings diff --git a/src/main.rs b/src/main.rs index 9d09c262..56c1d21b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,8 +87,10 @@ fn get_matches() -> clap::ArgMatches<'static> { (@arg REGEX_DEFAULT: -R --regex "Use regex in searching by default.") (@arg SHOW_DISABLED_DATA: -s --show_disabled_data "Show disabled data entries.") (@arg DEFAULT_TIME_VALUE: -t --default_time_value +takes_value "Default time value for graphs in milliseconds; minimum is 30s, defaults to 60s.") - (@arg TIME_DELTA: -i --time_delta +takes_value "The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s.") - (@group DEFAULT_WIDGET => + (@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 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.") diff --git a/src/options.rs b/src/options.rs index 77884e78..449cb66e 100644 --- a/src/options.rs +++ b/src/options.rs @@ -56,15 +56,15 @@ pub fn get_update_rate_in_milliseconds( update_rate: &Option<&str>, config: &Config, ) -> error::Result { let update_rate_in_milliseconds = if let Some(update_rate) = update_rate { - update_rate.parse::()? + update_rate.parse::()? } else if let Some(flags) = &config.flags { if let Some(rate) = flags.rate { - rate + rate as u128 } else { - DEFAULT_REFRESH_RATE_IN_MILLISECONDS + DEFAULT_REFRESH_RATE_IN_MILLISECONDS as u128 } } else { - DEFAULT_REFRESH_RATE_IN_MILLISECONDS + DEFAULT_REFRESH_RATE_IN_MILLISECONDS as u128 }; if update_rate_in_milliseconds < 250 { @@ -77,7 +77,7 @@ pub fn get_update_rate_in_milliseconds( )); } - Ok(update_rate_in_milliseconds) + Ok(update_rate_in_milliseconds as u64) } pub fn get_temperature_option( @@ -184,15 +184,15 @@ pub fn get_default_time_value_option( matches: &clap::ArgMatches<'static>, config: &Config, ) -> error::Result { let default_time = if let Some(default_time_value) = matches.value_of("DEFAULT_TIME_VALUE") { - default_time_value.parse::()? + default_time_value.parse::()? } else if let Some(flags) = &config.flags { if let Some(default_time_value) = flags.default_time_value { - default_time_value + default_time_value as u128 } else { - DEFAULT_TIME_MILLISECONDS + DEFAULT_TIME_MILLISECONDS as u128 } } else { - DEFAULT_TIME_MILLISECONDS + DEFAULT_TIME_MILLISECONDS as u128 }; if default_time < 30000 { @@ -205,35 +205,35 @@ pub fn get_default_time_value_option( )); } - Ok(default_time) + Ok(default_time as u64) } pub fn get_time_interval_option( matches: &clap::ArgMatches<'static>, config: &Config, ) -> error::Result { let time_interval = if let Some(time_interval) = matches.value_of("TIME_DELTA") { - time_interval.parse::()? + time_interval.parse::()? } else if let Some(flags) = &config.flags { if let Some(time_interval) = flags.time_delta { - time_interval + time_interval as u128 } else { - TIME_CHANGE_MILLISECONDS + TIME_CHANGE_MILLISECONDS as u128 } } else { - TIME_CHANGE_MILLISECONDS + TIME_CHANGE_MILLISECONDS as u128 }; if time_interval < 1000 { return Err(BottomError::InvalidArg( - "Please set your time interval to be at least 1 second.".to_string(), + "Please set your time delta to be at least 1 second.".to_string(), )); - } else if time_interval as u128 > std::u64::MAX as u128 { + } else if time_interval > std::u64::MAX as u128 { return Err(BottomError::InvalidArg( - "Please set your time interval to be at most unsigned INT_MAX.".to_string(), + "Please set your time delta to be at most unsigned INT_MAX.".to_string(), )); } - Ok(time_interval) + Ok(time_interval as u64) } pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { diff --git a/tests/arg_rate_tests.rs b/tests/arg_tests.rs similarity index 66% rename from tests/arg_rate_tests.rs rename to tests/arg_tests.rs index 7f22431c..f184b811 100644 --- a/tests/arg_rate_tests.rs +++ b/tests/arg_tests.rs @@ -34,6 +34,58 @@ fn test_small_rate() -> Result<(), Box> { Ok(()) } +#[test] +fn test_large_default_time() -> Result<(), Box> { + Command::new(get_os_binary_loc()) + .arg("-t") + .arg("18446744073709551616") + .assert() + .failure() + .stderr(predicate::str::contains( + "Please set your default value to be at most unsigned INT_MAX.", + )); + Ok(()) +} + +#[test] +fn test_small_default_time() -> Result<(), Box> { + Command::new(get_os_binary_loc()) + .arg("-t") + .arg("900") + .assert() + .failure() + .stderr(predicate::str::contains( + "Please set your default value to be at least 30 seconds.", + )); + Ok(()) +} + +#[test] +fn test_large_delta_time() -> Result<(), Box> { + Command::new(get_os_binary_loc()) + .arg("-d") + .arg("18446744073709551616") + .assert() + .failure() + .stderr(predicate::str::contains( + "Please set your time delta to be at most unsigned INT_MAX.", + )); + Ok(()) +} + +#[test] +fn test_small_delta_time() -> Result<(), Box> { + Command::new(get_os_binary_loc()) + .arg("-d") + .arg("900") + .assert() + .failure() + .stderr(predicate::str::contains( + "Please set your time delta to be at least 1 second.", + )); + Ok(()) +} + #[test] fn test_large_rate() -> Result<(), Box> { Command::new(get_os_binary_loc()) From e5588f16063ccb430104ff931719bb9aca27a359 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Mon, 9 Mar 2020 00:52:29 -0400 Subject: [PATCH 5/9] Add hiding time and autohiding time. --- README.md | 2 + src/app.rs | 87 ++++++++++++++++++++++++++--- src/canvas.rs | 81 ++++++++++++++------------- src/canvas/widgets/cpu_graph.rs | 35 +++++++++--- src/canvas/widgets/mem_graph.rs | 38 ++++++++++--- src/canvas/widgets/network_graph.rs | 37 +++++++++--- src/constants.rs | 5 +- src/main.rs | 4 +- src/options.rs | 34 +++++++++-- tests/arg_tests.rs | 4 +- 10 files changed, 250 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 35dd41b4..cbd15b90 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,8 @@ Note that `q` is disabled while in the search widget. - Scrolling with the mouse will scroll through the currently selected list if the widget is a scrollable table. +- Scrolling on a graph will zoom in (scroll up) or zoom out (scroll down). + ## Bugs and Requests Spot an bug? Have an idea? Leave an issue that explains what you want in detail and I'll try to take a look. diff --git a/src/app.rs b/src/app.rs index 1b1cffa2..7f98894e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -222,27 +222,31 @@ pub struct AppConfigFields { 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 NetworkState { +pub struct NetState { pub is_showing_tray: bool, pub is_showing_rx: bool, pub is_showing_tx: bool, pub zoom_level: f64, pub display_time: u64, pub force_update: bool, + pub display_time_instant: Option, } -impl Default for NetworkState { +impl Default for NetState { fn default() -> Self { - NetworkState { + NetState { is_showing_tray: false, is_showing_rx: true, is_showing_tx: true, zoom_level: 100.0, display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, + display_time_instant: None, } } } @@ -254,6 +258,7 @@ pub struct CpuState { pub core_show_vec: Vec, pub display_time: u64, pub force_update: bool, + pub display_time_instant: Option, } impl Default for CpuState { @@ -264,6 +269,7 @@ impl Default for CpuState { core_show_vec: Vec::new(), display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, + display_time_instant: None, } } } @@ -276,6 +282,7 @@ pub struct MemState { pub zoom_level: f64, pub display_time: u64, pub force_update: bool, + pub display_time_instant: Option, } impl Default for MemState { @@ -287,6 +294,7 @@ impl Default for MemState { zoom_level: 100.0, display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, + display_time_instant: None, } } } @@ -315,7 +323,7 @@ pub struct App { pub is_resized: bool, pub cpu_state: CpuState, pub mem_state: MemState, - pub net_state: NetworkState, + pub net_state: NetState, } impl App { @@ -330,7 +338,7 @@ impl App { ) -> App { let mut cpu_state = CpuState::default(); let mut mem_state = MemState::default(); - let mut net_state = NetworkState::default(); + let mut net_state = NetState::default(); cpu_state.display_time = default_time_value; mem_state.display_time = default_time_value; @@ -379,6 +387,8 @@ impl App { use_basic_mode, default_time_value, time_interval, + hide_time: false, + autohide_time: false, }, is_expanded: false, is_resized: false, @@ -1400,7 +1410,7 @@ impl App { 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), // TODO: [PO?] Temporary, may change if we add scaling + WidgetPosition::CpuLegend => self.change_cpu_table_position(-1), _ => {} } self.app_scroll_positions.scroll_direction = ScrollDirection::UP; @@ -1414,7 +1424,7 @@ impl App { 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), // TODO: [PO?] Temporary, may change if we add scaling + WidgetPosition::CpuLegend => self.change_cpu_table_position(1), _ => {} } self.app_scroll_positions.scroll_direction = ScrollDirection::DOWN; @@ -1507,6 +1517,15 @@ impl App { if new_time <= constants::STALE_MAX_MILLISECONDS { self.cpu_state.display_time = new_time; self.cpu_state.force_update = true; + if self.app_config_fields.autohide_time { + self.cpu_state.display_time_instant = Some(Instant::now()); + } + } else if self.cpu_state.display_time != constants::STALE_MAX_MILLISECONDS { + self.cpu_state.display_time = constants::STALE_MAX_MILLISECONDS; + self.cpu_state.force_update = true; + if self.app_config_fields.autohide_time { + self.cpu_state.display_time_instant = Some(Instant::now()); + } } } WidgetPosition::Mem => { @@ -1514,6 +1533,15 @@ impl App { if new_time <= constants::STALE_MAX_MILLISECONDS { self.mem_state.display_time = new_time; self.mem_state.force_update = true; + if self.app_config_fields.autohide_time { + self.mem_state.display_time_instant = Some(Instant::now()); + } + } else if self.mem_state.display_time != constants::STALE_MAX_MILLISECONDS { + self.mem_state.display_time = constants::STALE_MAX_MILLISECONDS; + self.mem_state.force_update = true; + if self.app_config_fields.autohide_time { + self.mem_state.display_time_instant = Some(Instant::now()); + } } } WidgetPosition::Network => { @@ -1521,6 +1549,15 @@ impl App { if new_time <= constants::STALE_MAX_MILLISECONDS { self.net_state.display_time = new_time; self.net_state.force_update = true; + if self.app_config_fields.autohide_time { + self.net_state.display_time_instant = Some(Instant::now()); + } + } else if self.net_state.display_time != constants::STALE_MAX_MILLISECONDS { + self.net_state.display_time = constants::STALE_MAX_MILLISECONDS; + self.net_state.force_update = true; + if self.app_config_fields.autohide_time { + self.net_state.display_time_instant = Some(Instant::now()); + } } } _ => {} @@ -1534,6 +1571,15 @@ impl App { if new_time >= constants::STALE_MIN_MILLISECONDS { self.cpu_state.display_time = new_time; self.cpu_state.force_update = true; + if self.app_config_fields.autohide_time { + self.cpu_state.display_time_instant = Some(Instant::now()); + } + } else if self.cpu_state.display_time != constants::STALE_MIN_MILLISECONDS { + self.cpu_state.display_time = constants::STALE_MIN_MILLISECONDS; + self.cpu_state.force_update = true; + if self.app_config_fields.autohide_time { + self.cpu_state.display_time_instant = Some(Instant::now()); + } } } WidgetPosition::Mem => { @@ -1541,6 +1587,15 @@ impl App { if new_time >= constants::STALE_MIN_MILLISECONDS { self.mem_state.display_time = new_time; self.mem_state.force_update = true; + if self.app_config_fields.autohide_time { + self.mem_state.display_time_instant = Some(Instant::now()); + } + } else if self.mem_state.display_time != constants::STALE_MIN_MILLISECONDS { + self.mem_state.display_time = constants::STALE_MIN_MILLISECONDS; + self.mem_state.force_update = true; + if self.app_config_fields.autohide_time { + self.mem_state.display_time_instant = Some(Instant::now()); + } } } WidgetPosition::Network => { @@ -1548,6 +1603,15 @@ impl App { if new_time >= constants::STALE_MIN_MILLISECONDS { self.net_state.display_time = new_time; self.net_state.force_update = true; + if self.app_config_fields.autohide_time { + self.net_state.display_time_instant = Some(Instant::now()); + } + } else if self.net_state.display_time != constants::STALE_MIN_MILLISECONDS { + self.net_state.display_time = constants::STALE_MIN_MILLISECONDS; + self.net_state.force_update = true; + if self.app_config_fields.autohide_time { + self.net_state.display_time_instant = Some(Instant::now()); + } } } _ => {} @@ -1559,14 +1623,23 @@ impl App { WidgetPosition::Cpu => { self.cpu_state.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.display_time_instant = Some(Instant::now()); + } } WidgetPosition::Mem => { self.mem_state.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.display_time_instant = Some(Instant::now()); + } } WidgetPosition::Network => { self.net_state.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.display_time_instant = Some(Instant::now()); + } } _ => {} } diff --git a/src/canvas.rs b/src/canvas.rs index aa657ab2..a5703f3d 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -72,44 +72,47 @@ pub struct Painter { impl Painter { /// Must be run once before drawing, but after setting colours. /// This is to set some remaining styles and text. - /// This bypasses some logic checks (size > 2, for example) but this - /// assumes that you, the programmer, are sane and do not do stupid things. - /// RIGHT? pub fn initialize(&mut self) { self.is_mac_os = cfg!(target_os = "macos"); - self.styled_general_help_text.push(Text::Styled( - GENERAL_HELP_TEXT[0].into(), - self.colours.table_header_style, - )); - self.styled_general_help_text.extend( - GENERAL_HELP_TEXT[1..] - .iter() - .map(|&text| Text::Styled(text.into(), self.colours.text_style)) - .collect::>(), - ); + if GENERAL_HELP_TEXT.len() > 1 { + self.styled_general_help_text.push(Text::Styled( + GENERAL_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_general_help_text.extend( + GENERAL_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); + } - self.styled_process_help_text.push(Text::Styled( - PROCESS_HELP_TEXT[0].into(), - self.colours.table_header_style, - )); - self.styled_process_help_text.extend( - PROCESS_HELP_TEXT[1..] - .iter() - .map(|&text| Text::Styled(text.into(), self.colours.text_style)) - .collect::>(), - ); + if PROCESS_HELP_TEXT.len() > 1 { + self.styled_process_help_text.push(Text::Styled( + PROCESS_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_process_help_text.extend( + PROCESS_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); + } - self.styled_search_help_text.push(Text::Styled( - SEARCH_HELP_TEXT[0].into(), - self.colours.table_header_style, - )); - self.styled_search_help_text.extend( - SEARCH_HELP_TEXT[1..] - .iter() - .map(|&text| Text::Styled(text.into(), self.colours.text_style)) - .collect::>(), - ); + if SEARCH_HELP_TEXT.len() > 1 { + self.styled_search_help_text.push(Text::Styled( + SEARCH_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_search_help_text.extend( + SEARCH_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); + } } pub fn draw_specific_table( @@ -260,11 +263,11 @@ impl Painter { 0 }; - self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]); + 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]); + self.draw_memory_graph(&mut f, app_state, rect[0]); } WidgetPosition::Disk => { self.draw_disk_table(&mut f, app_state, rect[0], true); @@ -275,7 +278,7 @@ impl Painter { WidgetPosition::Network | WidgetPosition::BasicNet | WidgetPosition::NetworkLegend => { - self.draw_network_graph(&mut f, &app_state, rect[0]); + 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); @@ -408,10 +411,10 @@ impl Painter { 0 }; - self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]); + 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_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); diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 59cee390..462cbf44 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -31,25 +31,46 @@ lazy_static! { } pub trait CpuGraphWidget { - fn draw_cpu_graph(&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect); + fn draw_cpu_graph(&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect); fn draw_cpu_legend( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, ); } impl CpuGraphWidget for Painter { - fn draw_cpu_graph(&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect) { + fn draw_cpu_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + ) { let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; let display_time_labels = [ format!("{}s", app_state.cpu_state.display_time / 1000), "0s".to_string(), ]; - let x_axis = Axis::default() - .bounds([0.0, app_state.cpu_state.display_time as f64]) - .style(self.colours.graph_style) - .labels_style(self.colours.graph_style) - .labels(&display_time_labels); + let x_axis = if app_state.app_config_fields.hide_time + || app_state.cpu_state.display_time_instant.is_none() + { + Axis::default().bounds([0.0, app_state.cpu_state.display_time as f64]) + } else if let Some(time) = app_state.cpu_state.display_time_instant { + if std::time::Instant::now().duration_since(time).as_millis() + < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + { + Axis::default() + .bounds([0.0, app_state.cpu_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + } else { + app_state.cpu_state.display_time_instant = None; + Axis::default().bounds([0.0, app_state.cpu_state.display_time as f64]) + } + } else { + Axis::default() + .bounds([0.0, app_state.cpu_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + }; // Note this is offset as otherwise the 0 value is not drawn! let y_axis = Axis::default() diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index 58ca8545..77062e14 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -3,6 +3,7 @@ use std::cmp::max; use crate::{ app::{App, WidgetPosition}, canvas::Painter, + constants::*, }; use tui::{ @@ -13,11 +14,15 @@ use tui::{ }; pub trait MemGraphWidget { - fn draw_memory_graph(&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect); + fn draw_memory_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + ); } impl MemGraphWidget for Painter { - fn draw_memory_graph(&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect) { + fn draw_memory_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + ) { let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data; let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; @@ -25,11 +30,30 @@ impl MemGraphWidget for Painter { format!("{}s", app_state.mem_state.display_time / 1000), "0s".to_string(), ]; - let x_axis = Axis::default() - .bounds([0.0, app_state.mem_state.display_time as f64]) - .style(self.colours.graph_style) - .labels_style(self.colours.graph_style) - .labels(&display_time_labels); + let x_axis = if app_state.app_config_fields.hide_time + || app_state.mem_state.display_time_instant.is_none() + { + Axis::default().bounds([0.0, app_state.mem_state.display_time as f64]) + } else if let Some(time) = app_state.mem_state.display_time_instant { + if std::time::Instant::now().duration_since(time).as_millis() + < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + { + Axis::default() + .bounds([0.0, app_state.mem_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + } else { + app_state.mem_state.display_time_instant = None; + Axis::default().bounds([0.0, app_state.mem_state.display_time as f64]) + } + } else { + Axis::default() + .bounds([0.0, app_state.mem_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + }; // Offset as the zero value isn't drawn otherwise... let y_axis: Axis<'_, &str> = Axis::default() diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index c22f3847..d5ec1865 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -23,7 +23,9 @@ lazy_static! { } pub trait NetworkGraphWidget { - fn draw_network_graph(&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect); + fn draw_network_graph( + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, + ); fn draw_network_labels( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, @@ -32,7 +34,7 @@ pub trait NetworkGraphWidget { impl NetworkGraphWidget for Painter { fn draw_network_graph( - &self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect, + &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, ) { 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; @@ -41,13 +43,32 @@ impl NetworkGraphWidget for Painter { format!("{}s", app_state.net_state.display_time / 1000), "0s".to_string(), ]; - let x_axis = Axis::default() - .bounds([0.0, app_state.net_state.display_time as f64]) - .style(self.colours.graph_style) - .labels_style(self.colours.graph_style) - .labels(&display_time_labels); + let x_axis = if app_state.app_config_fields.hide_time + || app_state.net_state.display_time_instant.is_none() + { + Axis::default().bounds([0.0, app_state.net_state.display_time as f64]) + } else if let Some(time) = app_state.net_state.display_time_instant { + if std::time::Instant::now().duration_since(time).as_millis() + < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 + { + Axis::default() + .bounds([0.0, app_state.net_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + } else { + app_state.net_state.display_time_instant = None; + Axis::default().bounds([0.0, app_state.net_state.display_time as f64]) + } + } else { + Axis::default() + .bounds([0.0, app_state.net_state.display_time as f64]) + .style(self.colours.graph_style) + .labels_style(self.colours.graph_style) + .labels(&display_time_labels) + }; - // 0 is offset. + // 0 is offset. let y_axis: Axis<'_, &str> = Axis::default() .style(self.colours.graph_style) .labels_style(self.colours.graph_style) diff --git a/src/constants.rs b/src/constants.rs index f488ccfe..da98c16f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,10 +1,11 @@ // How long to store data. -pub const STALE_MAX_MILLISECONDS: u64 = 300 * 1000; // Keep 5 minutes of data. +pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data. // How much data is SHOWN pub const DEFAULT_TIME_MILLISECONDS: u64 = 60 * 1000; // Defaults to 1 min. pub const STALE_MIN_MILLISECONDS: u64 = 30 * 1000; // Lowest is 30 seconds pub const TIME_CHANGE_MILLISECONDS: u64 = 15 * 1000; // How much to increment each time +pub const AUTOHIDE_TIMEOUT_MILLISECONDS: u64 = 5000; // 5 seconds to autohide pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; // How fast the screen refreshes @@ -143,7 +144,7 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" #default_time_value = 60000 # The time delta on each zoom in/out action (in milliseconds). -# time_delta = 15000 +#time_delta = 15000 # 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 diff --git a/src/main.rs b/src/main.rs index 56c1d21b..c0746eac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,7 +89,7 @@ fn get_matches() -> clap::ArgMatches<'static> { (@arg DEFAULT_TIME_VALUE: -t --default_time_value +takes_value "Default time value for graphs in milliseconds; minimum is 30s, defaults to 60s.") (@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 then this will have no effect.") + (@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.") @@ -143,6 +143,8 @@ fn main() -> error::Result<()> { 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); + enable_hide_time(&matches, &config, &mut app); + enable_autohide_time(&matches, &config, &mut app); // Set up up tui and crossterm let mut stdout_val = stdout(); diff --git a/src/options.rs b/src/options.rs index 449cb66e..a5cd832e 100644 --- a/src/options.rs +++ b/src/options.rs @@ -29,6 +29,8 @@ pub struct ConfigFlags { pub basic: Option, pub default_time_value: Option, pub time_delta: Option, + pub autohide_time: Option, + pub hide_time: Option, //disabled_cpu_cores: Option>, // TODO: [FEATURE] Enable disabling cores in config/flags } @@ -199,9 +201,9 @@ pub fn get_default_time_value_option( return Err(BottomError::InvalidArg( "Please set your default value to be at least 30 seconds.".to_string(), )); - } else if default_time as u128 > std::u64::MAX as u128 { + } else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 { return Err(BottomError::InvalidArg( - "Please set your default value to be at most unsigned INT_MAX.".to_string(), + "Please set your default value to be at most 10 minutes.".to_string(), )); } @@ -227,9 +229,9 @@ pub fn get_time_interval_option( return Err(BottomError::InvalidArg( "Please set your time delta to be at least 1 second.".to_string(), )); - } else if time_interval > std::u64::MAX as u128 { + } else if time_interval > STALE_MAX_MILLISECONDS as u128 { return Err(BottomError::InvalidArg( - "Please set your time delta to be at most unsigned INT_MAX.".to_string(), + "Please set your time delta to be at most 10 minutes.".to_string(), )); } @@ -288,6 +290,30 @@ pub fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config } } +pub fn enable_hide_time(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { + if matches.is_present("HIDE_TIME") { + app.app_config_fields.hide_time = true; + } else if let Some(flags) = &config.flags { + if let Some(hide_time) = flags.hide_time { + if hide_time { + app.app_config_fields.hide_time = true; + } + } + } +} + +pub fn enable_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { + if matches.is_present("AUTOHIDE_TIME") { + app.app_config_fields.autohide_time = true; + } else if let Some(flags) = &config.flags { + if let Some(autohide_time) = flags.autohide_time { + if autohide_time { + app.app_config_fields.autohide_time = true; + } + } + } +} + pub fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition { if matches.is_present("CPU_WIDGET") { return WidgetPosition::Cpu; diff --git a/tests/arg_tests.rs b/tests/arg_tests.rs index f184b811..a316879e 100644 --- a/tests/arg_tests.rs +++ b/tests/arg_tests.rs @@ -42,7 +42,7 @@ fn test_large_default_time() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your default value to be at most unsigned INT_MAX.", + "Please set your default value to be at most 10 minutes.", )); Ok(()) } @@ -68,7 +68,7 @@ fn test_large_delta_time() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your time delta to be at most unsigned INT_MAX.", + "Please set your time delta to be at most 10 minutes.", )); Ok(()) } From 104604313882d2e5a36307a9d96a09a4a55e1df2 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 10 Mar 2020 01:26:30 -0400 Subject: [PATCH 6/9] Fix bug with autohide causing hide to break. --- README.md | 4 +++- src/canvas/widgets/cpu_graph.rs | 4 +++- src/canvas/widgets/mem_graph.rs | 3 ++- src/canvas/widgets/network_graph.rs | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cbd15b90..4bdd90d0 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,9 @@ Features of bottom include: - Maximizing of widgets of interest to take up the entire window. -- Basic mode +- A minimal mode that focuses less on graphs and more on data, similar to [htop](https://hisham.hm/htop/). + +- Zooming in/out to see more/less data. More details about each widget and compatibility can be found [here](./docs/widgets.md). diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 462cbf44..c0cbb371 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -47,8 +47,10 @@ impl CpuGraphWidget for Painter { format!("{}s", app_state.cpu_state.display_time / 1000), "0s".to_string(), ]; + let x_axis = if app_state.app_config_fields.hide_time - || app_state.cpu_state.display_time_instant.is_none() + || (app_state.app_config_fields.autohide_time + && app_state.cpu_state.display_time_instant.is_none()) { Axis::default().bounds([0.0, app_state.cpu_state.display_time as f64]) } else if let Some(time) = app_state.cpu_state.display_time_instant { diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index 77062e14..d30e88e3 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -31,7 +31,8 @@ impl MemGraphWidget for Painter { "0s".to_string(), ]; let x_axis = if app_state.app_config_fields.hide_time - || app_state.mem_state.display_time_instant.is_none() + || (app_state.app_config_fields.autohide_time + && app_state.mem_state.display_time_instant.is_none()) { Axis::default().bounds([0.0, app_state.mem_state.display_time as f64]) } else if let Some(time) = app_state.mem_state.display_time_instant { diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index d5ec1865..f8abff28 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -44,7 +44,8 @@ impl NetworkGraphWidget for Painter { "0s".to_string(), ]; let x_axis = if app_state.app_config_fields.hide_time - || app_state.net_state.display_time_instant.is_none() + || (app_state.app_config_fields.autohide_time + && app_state.net_state.display_time_instant.is_none()) { Axis::default().bounds([0.0, app_state.net_state.display_time as f64]) } else if let Some(time) = app_state.net_state.display_time_instant { From 8630287676baad0f5b50ecf41dcde652128f6d00 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 10 Mar 2020 01:38:37 -0400 Subject: [PATCH 7/9] Default to showing time at first for autohide. --- src/app.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7f98894e..1f961570 100644 --- a/src/app.rs +++ b/src/app.rs @@ -246,7 +246,7 @@ impl Default for NetState { zoom_level: 100.0, display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, - display_time_instant: None, + display_time_instant: Some(Instant::now()), } } } @@ -269,7 +269,7 @@ impl Default for CpuState { core_show_vec: Vec::new(), display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, - display_time_instant: None, + display_time_instant: Some(Instant::now()), } } } @@ -294,7 +294,7 @@ impl Default for MemState { zoom_level: 100.0, display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, - display_time_instant: None, + display_time_instant: Some(Instant::now()), } } } From 46f1b7df0031efbf7ddd05aef4c486bfd1ddaa71 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 10 Mar 2020 01:43:42 -0400 Subject: [PATCH 8/9] Redid how we auto-set time if we have autohide on. --- src/app.rs | 6 +++--- src/options.rs | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1f961570..7f98894e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -246,7 +246,7 @@ impl Default for NetState { zoom_level: 100.0, display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, - display_time_instant: Some(Instant::now()), + display_time_instant: None, } } } @@ -269,7 +269,7 @@ impl Default for CpuState { core_show_vec: Vec::new(), display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, - display_time_instant: Some(Instant::now()), + display_time_instant: None, } } } @@ -294,7 +294,7 @@ impl Default for MemState { zoom_level: 100.0, display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, - display_time_instant: Some(Instant::now()), + display_time_instant: None, } } } diff --git a/src/options.rs b/src/options.rs index a5cd832e..654a9356 100644 --- a/src/options.rs +++ b/src/options.rs @@ -305,10 +305,18 @@ pub fn enable_hide_time(matches: &clap::ArgMatches<'static>, config: &Config, ap pub fn enable_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { if matches.is_present("AUTOHIDE_TIME") { app.app_config_fields.autohide_time = true; + let time = Some(std::time::Instant::now()); + app.cpu_state.display_time_instant = time; + app.mem_state.display_time_instant = time; + app.net_state.display_time_instant = time; } else if let Some(flags) = &config.flags { if let Some(autohide_time) = flags.autohide_time { if autohide_time { app.app_config_fields.autohide_time = true; + let time = Some(std::time::Instant::now()); + app.cpu_state.display_time_instant = time; + app.mem_state.display_time_instant = time; + app.net_state.display_time_instant = time; } } } From 648864176fae3ada68cbeceaeddfc004ecd03726 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 10 Mar 2020 01:51:11 -0400 Subject: [PATCH 9/9] Updated documentation, made error in args clearer --- README.md | 6 ++++++ src/options.rs | 8 ++++---- tests/arg_tests.rs | 8 ++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4bdd90d0..ba02b4ff 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,12 @@ Run using `btm`. - `Enter` on a widget to maximize the widget. +- `+` to zoom in (reduce time interval, smallest is 30 seconds). + +- `-` to zoom out (increase time interval, largest is 10 minutes). + +- `=` to reset zoom. + #### CPU - `/` to allow for enabling/disabling showing certain cores with `Space`. diff --git a/src/options.rs b/src/options.rs index 654a9356..b91be73a 100644 --- a/src/options.rs +++ b/src/options.rs @@ -199,11 +199,11 @@ pub fn get_default_time_value_option( if default_time < 30000 { return Err(BottomError::InvalidArg( - "Please set your default value to be at least 30 seconds.".to_string(), + "Please set your default value to be at least 30000 milliseconds.".to_string(), )); } else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 { return Err(BottomError::InvalidArg( - "Please set your default value to be at most 10 minutes.".to_string(), + format!("Please set your default value to be at most {} milliseconds.", STALE_MAX_MILLISECONDS), )); } @@ -227,11 +227,11 @@ pub fn get_time_interval_option( if time_interval < 1000 { return Err(BottomError::InvalidArg( - "Please set your time delta to be at least 1 second.".to_string(), + "Please set your time delta to be at least 1000 milliseconds.".to_string(), )); } else if time_interval > STALE_MAX_MILLISECONDS as u128 { return Err(BottomError::InvalidArg( - "Please set your time delta to be at most 10 minutes.".to_string(), + format!("Please set your time delta to be at most {} milliseconds.", STALE_MAX_MILLISECONDS), )); } diff --git a/tests/arg_tests.rs b/tests/arg_tests.rs index a316879e..627b1cbe 100644 --- a/tests/arg_tests.rs +++ b/tests/arg_tests.rs @@ -42,7 +42,7 @@ fn test_large_default_time() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your default value to be at most 10 minutes.", + "Please set your default value to be at most", )); Ok(()) } @@ -55,7 +55,7 @@ fn test_small_default_time() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your default value to be at least 30 seconds.", + "Please set your default value to be at least", )); Ok(()) } @@ -68,7 +68,7 @@ fn test_large_delta_time() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your time delta to be at most 10 minutes.", + "Please set your time delta to be at most", )); Ok(()) } @@ -81,7 +81,7 @@ fn test_small_delta_time() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your time delta to be at least 1 second.", + "Please set your time delta to be at least", )); Ok(()) }