From 863e780f2f8f09fb7244bcf9819694f50cc1a400 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Fri, 24 Apr 2020 19:17:58 -0400 Subject: [PATCH] change: add scrolling to help menu --- CHANGELOG.md | 20 ++-- README.md | 21 ++-- src/app.rs | 103 ++++++++++++++------ src/canvas.rs | 128 +++++++++++++++---------- src/canvas/dialogs/filter_dialog.rs | 0 src/canvas/dialogs/help_dialog.rs | 144 ++++++++++++++++++++++------ src/canvas/drawing_utils.rs | 9 +- src/canvas/widgets/cpu_graph.rs | 2 +- src/canvas/widgets/disk_table.rs | 2 +- src/canvas/widgets/process_table.rs | 4 +- src/canvas/widgets/temp_table.rs | 2 +- src/constants.rs | 88 ++++++++++------- src/main.rs | 22 ++--- 13 files changed, 373 insertions(+), 172 deletions(-) create mode 100644 src/canvas/dialogs/filter_dialog.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2a0ef2..80f59a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,12 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `"processes"` - `"temperature"` -- Removed an (undocumented) feature in allowing modifying total RX/TX colours. This is mainly due to the legend change. - -- Updated error messages to be a bit more consistent/helpful. - - [#117](https://github.com/ClementTsang/bottom/issues/117): Update tui to 0.9: + - Removed an (undocumented) feature in allowing modifying total RX/TX colours. This is mainly due to the legend change. + - Use custom legend-hiding to stop hiding legends for memory and network widgets. - In addition, changed to using only legends within the graph for network, as well as redesigned the legend. @@ -40,9 +38,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow for option to hide the header gap on tables via `--hide_table_gap` or `hide_table_gap = true`. - - Switch to stateful widget style for tables. +- [#126](https://github.com/ClementTsang/bottom/pull/126): Updated error messages to be a bit more consistent/helpful. - - Switch to using tui-rs' new built in linear interpolation rather than doing it manually. +- Redesigned help menu to allow for scrolling. ### Bug Fixes @@ -51,9 +49,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed bug where a single empty row as a layout would crash without a proper warning. The behaviour now errors out with a more helpful message. -### Other +### Development changes -- Updated tests and added config testing. +- Switch to stateful widget style for tables. + +- Switch to using tui-rs' new built in linear interpolation rather than doing it manually. + +- Updated arg tests and added config testing. + +- More refactoring. ## [0.3.0] - 2020-04-07 diff --git a/README.md b/README.md index 8767a79e..a5e23fe1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows. Inspired by both [gtop](https://github.com/aksakalli/gtop) and [gotop](https://github.com/cjbassi/gotop). + + ![Quick demo recording showing off searching, maximizing, and process killing.](assets/summary_and_search.gif) _Theme based on [gruvbox](https://github.com/morhetz/gruvbox) (see [sample config](./sample_configs/demo_config.toml))._ Recorded on version 0.2.0. **Note**: This documentation is relevant to version 0.4.0 and may refer to in-development features, especially if you are reading this on the master branch. Please refer to [release branch](https://github.com/ClementTsang/bottom/tree/release/README.md) or [crates.io](https://crates.io/crates/bottom) for the most up-to-date _release_ documentation. @@ -163,19 +165,19 @@ Run using `btm`. | | | | -------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| `q`, `Ctrl-c` | Quit bottom | +| `q`, `Ctrl-c` | Quit | | `Esc` | Close dialog windows, search, widgets, or exit maximized mode | | `Ctrl-r` | Reset display and any collected data | | `f` | Freeze/unfreeze updating with new data | | `Ctrl`-arrow key
`Shift`-arrow key
`H/J/K/L` | Move to a different widget (on macOS some keybindings may conflict) | -| `Up`,`k` | Scroll up in tables | -| `Down`, `j` | Scroll down in tables | +| `Up`,`k` | Scroll up | +| `Down`, `j` | Scroll down | | `?` | Open help menu | -| `gg`, `Home` | Jump to the first entry of a table | -| `Shift-g`, `End` | Jump to the last entry of a table | -| `Enter` | Maximize widget | -| `+` | Zoom in on a chart | -| `-` | Zoom out on a chart | +| `gg`, `Home` | Jump to the first entry | +| `Shift-g`, `End` | Jump to the last entry | +| `Enter` | Maximize the currently selected widget | +| `+` | Zoom in on chart (decrease time range) | +| `-` | Zoom out on chart (increase time range) | | `=` | Reset zoom | | Mouse scroll | Table: Scrolls through the list
Chart: Zooms in or out by scrolling up or down respectively | @@ -207,6 +209,9 @@ Run using `btm`. | `Esc` | Close the search widget (retains the filter) | | `Ctrl-a` | Skip to the start of the search query | | `Ctrl-e` | Skip to the end of the search query | +| `Ctrl-u` | Clear the current search query | +| `Backspace` | Delete the character behind the cursor | +| `Delete` | Delete the character at the cursor | | `Alt-c`/`F1` | Toggle matching case | | `Alt-w`/`F2` | Toggle matching the entire word | | `Alt-r`/`F3` | Toggle using regex | diff --git a/src/app.rs b/src/app.rs index 4f32b398..a71c4192 100644 --- a/src/app.rs +++ b/src/app.rs @@ -64,18 +64,15 @@ pub enum AppHelpCategory { Search, } +#[derive(Default)] pub struct AppHelpDialogState { pub is_showing_help: bool, - pub current_category: AppHelpCategory, -} - -impl Default for AppHelpDialogState { - fn default() -> Self { - AppHelpDialogState { - is_showing_help: false, - current_category: AppHelpCategory::General, - } - } + pub scroll_state: ParagraphScrollState, + pub general_index: u16, + pub cpu_index: u16, + pub process_index: u16, + pub search_index: u16, + pub battery_index: u16, } /// AppConfigFields is meant to cover basic fields that would normally be set @@ -481,6 +478,12 @@ impl BatteryState { } } +#[derive(Default)] +pub struct ParagraphScrollState { + pub current_scroll_index: u16, + pub max_scroll_index: u16, +} + #[derive(TypedBuilder)] pub struct App { #[builder(default = false, setter(skip))] @@ -517,7 +520,7 @@ pub struct App { pub is_expanded: bool, #[builder(default = false, setter(skip))] - pub is_resized: bool, + pub is_force_redraw: bool, pub cpu_state: CpuState, pub mem_state: MemState, @@ -581,11 +584,11 @@ impl App { self.reset_multi_tap_keys(); if self.is_in_dialog() { self.help_dialog_state.is_showing_help = false; - self.help_dialog_state.current_category = AppHelpCategory::General; self.delete_dialog_state.is_showing_dd = false; self.delete_dialog_state.is_on_yes = false; self.to_delete_process_list = None; self.dd_err = None; + self.is_force_redraw = true; } else if self.is_filtering_or_searching() { match self.current_widget.widget_type { BottomWidgetType::Cpu => { @@ -603,7 +606,7 @@ impl App { cpu_widget_state.scroll_state.current_scroll_position = new_position; cpu_widget_state.scroll_state.previous_scroll_position = 0; } - self.is_resized = true; + self.is_force_redraw = true; } } BottomWidgetType::CpuLegend => { @@ -621,7 +624,7 @@ impl App { cpu_widget_state.scroll_state.current_scroll_position = new_position; cpu_widget_state.scroll_state.previous_scroll_position = 0; } - self.is_resized = true; + self.is_force_redraw = true; } } BottomWidgetType::Proc => { @@ -636,7 +639,7 @@ impl App { .search_state .is_enabled = false; } - self.is_resized = true; + self.is_force_redraw = true; } } BottomWidgetType::ProcSearch => { @@ -652,14 +655,14 @@ impl App { .is_enabled = false; self.move_widget_selection_up(); } - self.is_resized = true; + self.is_force_redraw = true; } } _ => {} } } else if self.is_expanded { self.is_expanded = false; - self.is_resized = true; + self.is_force_redraw = true; } } @@ -974,7 +977,7 @@ impl App { BottomWidgetType::ProcSearch => {} _ => { self.is_expanded = true; - self.is_resized = true; + self.is_force_redraw = true; } } } @@ -1098,13 +1101,19 @@ impl App { pub fn on_up_key(&mut self) { if !self.is_in_dialog() { self.decrement_position_count(); + } else if self.help_dialog_state.is_showing_help { + self.help_scroll_up(); } + self.reset_multi_tap_keys(); } pub fn on_down_key(&mut self) { if !self.is_in_dialog() { self.increment_position_count(); + } else if self.help_dialog_state.is_showing_help { + self.help_scroll_down(); } + self.reset_multi_tap_keys(); } pub fn on_left_key(&mut self) { @@ -1428,10 +1437,16 @@ impl App { } self.handle_char(caught_char); } else if self.help_dialog_state.is_showing_help { + // TODO: Seems weird that we have it like this; it would be better to make this + // more obvious that we are separating dialog logic and normal logic IMO. + // This is even more so as most logic already checks for dialog state. match caught_char { - '1' => self.help_dialog_state.current_category = AppHelpCategory::General, - '2' => self.help_dialog_state.current_category = AppHelpCategory::Process, - '3' => self.help_dialog_state.current_category = AppHelpCategory::Search, + '1' => self.help_scroll_to_or_max(self.help_dialog_state.general_index), + '2' => self.help_scroll_to_or_max(self.help_dialog_state.cpu_index), + '3' => self.help_scroll_to_or_max(self.help_dialog_state.process_index), + '4' => self.help_scroll_to_or_max(self.help_dialog_state.search_index), + '5' => self.help_scroll_to_or_max(self.help_dialog_state.battery_index), + 'j' | 'k' | 'g' | 'G' => self.handle_char(caught_char), _ => {} } } @@ -1478,8 +1493,8 @@ impl App { } } 'G' => self.skip_to_last(), - 'k' => self.decrement_position_count(), - 'j' => self.increment_position_count(), + 'k' => self.on_up_key(), + 'j' => self.on_down_key(), 'f' => { self.is_frozen = !self.is_frozen; if self.is_frozen { @@ -1584,6 +1599,7 @@ impl App { } '?' => { self.help_dialog_state.is_showing_help = true; + self.is_force_redraw = true; } 'H' => self.move_widget_selection_left(), 'L' => self.move_widget_selection_right(), @@ -2019,6 +2035,8 @@ impl App { _ => {} } self.reset_multi_tap_keys(); + } else { + self.help_dialog_state.scroll_state.current_scroll_index = 0; } } @@ -2093,6 +2111,12 @@ impl App { _ => {} } self.reset_multi_tap_keys(); + } else { + self.help_dialog_state.scroll_state.current_scroll_index = self + .help_dialog_state + .scroll_state + .max_scroll_index + .saturating_sub(1); } } @@ -2105,7 +2129,6 @@ impl App { BottomWidgetType::CpuLegend => self.change_cpu_table_position(-1), _ => {} } - self.reset_multi_tap_keys(); } } @@ -2118,7 +2141,6 @@ impl App { BottomWidgetType::CpuLegend => self.change_cpu_table_position(1), _ => {} } - self.reset_multi_tap_keys(); } } @@ -2228,8 +2250,33 @@ impl App { } } + fn help_scroll_up(&mut self) { + if self.help_dialog_state.scroll_state.current_scroll_index > 0 { + self.help_dialog_state.scroll_state.current_scroll_index -= 1; + } + } + + fn help_scroll_down(&mut self) { + if self.help_dialog_state.scroll_state.current_scroll_index + 1 + < self.help_dialog_state.scroll_state.max_scroll_index + { + self.help_dialog_state.scroll_state.current_scroll_index += 1; + } + } + + fn help_scroll_to_or_max(&mut self, new_position: u16) { + if new_position < self.help_dialog_state.scroll_state.max_scroll_index { + self.help_dialog_state.scroll_state.current_scroll_index = new_position; + } else { + self.help_dialog_state.scroll_state.current_scroll_index = + self.help_dialog_state.scroll_state.max_scroll_index - 1; + } + } + pub fn handle_scroll_up(&mut self) { - if self.current_widget.widget_type.is_widget_graph() { + if self.help_dialog_state.is_showing_help { + self.help_scroll_up(); + } else if self.current_widget.widget_type.is_widget_graph() { self.zoom_in(); } else if self.current_widget.widget_type.is_widget_table() { self.decrement_position_count(); @@ -2237,7 +2284,9 @@ impl App { } pub fn handle_scroll_down(&mut self) { - if self.current_widget.widget_type.is_widget_graph() { + if self.help_dialog_state.is_showing_help { + self.help_scroll_down(); + } else if self.current_widget.widget_type.is_widget_graph() { self.zoom_out(); } else if self.current_widget.widget_type.is_widget_table() { self.increment_position_count(); diff --git a/src/canvas.rs b/src/canvas.rs index 9e4aaf30..ad332655 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -59,9 +59,7 @@ pub struct Painter { pub colours: CanvasColours, height: u16, width: u16, - styled_general_help_text: Vec>, - styled_process_help_text: Vec>, - styled_search_help_text: Vec>, + styled_help_text: Vec>, is_mac_os: bool, row_constraints: Vec, col_constraints: Vec>, @@ -145,9 +143,7 @@ impl Painter { colours: CanvasColours::default(), height: 0, width: 0, - styled_general_help_text: Vec::new(), - styled_process_help_text: Vec::new(), - styled_search_help_text: Vec::new(), + styled_help_text: Vec::new(), is_mac_os: false, row_constraints, col_constraints, @@ -164,44 +160,79 @@ impl Painter { pub fn complete_painter_init(&mut self) { self.is_mac_os = cfg!(target_os = "macos"); - 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::>(), - ); - } + // Init help text: + // ToC + self.styled_help_text.extend( + HELP_CONTENTS_TEXT + .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::>(), - ); - } + // General + self.styled_help_text.push(Text::Raw("\n\n".into())); + self.styled_help_text.push(Text::Styled( + GENERAL_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_help_text.extend( + GENERAL_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::>(), - ); - } + // CPU + self.styled_help_text.push(Text::Raw("\n\n".into())); + self.styled_help_text.push(Text::Styled( + CPU_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_help_text.extend( + CPU_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); + + // Proc + self.styled_help_text.push(Text::Raw("\n\n".into())); + self.styled_help_text.push(Text::Styled( + PROCESS_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_help_text.extend( + PROCESS_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); + + // Proc Search + self.styled_help_text.push(Text::Raw("\n\n".into())); + self.styled_help_text.push(Text::Styled( + SEARCH_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_help_text.extend( + SEARCH_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); + + // Battery + self.styled_help_text.push(Text::Raw("\n\n".into())); + self.styled_help_text.push(Text::Styled( + BATTERY_HELP_TEXT[0].into(), + self.colours.table_header_style, + )); + self.styled_help_text.extend( + BATTERY_HELP_TEXT[1..] + .iter() + .map(|&text| Text::Styled(text.into(), self.colours.text_style)) + .collect::>(), + ); } // TODO: [FEATURE] Auto-resizing dialog sizes. @@ -214,11 +245,10 @@ impl Painter { let current_height = terminal_size.height; let current_width = terminal_size.width; - if self.height == 0 && self.width == 0 { - self.height = current_height; - self.width = current_width; - } else if self.height != current_height || self.width != current_width { - app_state.is_resized = true; + if (self.height == 0 && self.width == 0) + || (self.height != current_height || self.width != current_width) + { + app_state.is_force_redraw = true; self.height = current_height; self.width = current_width; } @@ -434,7 +464,7 @@ impl Painter { } } else { // Draws using the passed in (or default) layout. NOT basic so far. - if self.derived_widget_draw_locs.is_empty() || app_state.is_resized { + if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw { let row_draw_locs = Layout::default() .margin(0) .constraints(self.row_constraints.as_ref()) @@ -531,7 +561,7 @@ impl Painter { } })?; - app_state.is_resized = false; + app_state.is_force_redraw = false; Ok(()) } diff --git a/src/canvas/dialogs/filter_dialog.rs b/src/canvas/dialogs/filter_dialog.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/canvas/dialogs/help_dialog.rs b/src/canvas/dialogs/help_dialog.rs index 8dbca876..70492ae9 100644 --- a/src/canvas/dialogs/help_dialog.rs +++ b/src/canvas/dialogs/help_dialog.rs @@ -1,4 +1,5 @@ use std::cmp::max; +use unicode_width::UnicodeWidthStr; use tui::{ backend::Backend, @@ -7,12 +8,9 @@ use tui::{ widgets::{Block, Borders, Paragraph}, }; -use crate::{ - app::{App, AppHelpCategory}, - canvas::Painter, -}; +use crate::{app::App, canvas::Painter, constants}; -const HELP_BASE: &str = " Help ── 1: General ─── 2: Processes ─── 3: Search ─── Esc to close "; +const HELP_BASE: &str = " Help ── Esc to close "; pub trait HelpDialog { fn draw_help_dialog( @@ -28,31 +26,121 @@ impl HelpDialog for Painter { 0, draw_loc.width as i32 - HELP_BASE.chars().count() as i32 - 2, ); - let help_title = format!( - " Help ─{}─ 1: General ─── 2: Processes ─── 3: Search ─── Esc to close ", - "─".repeat(repeat_num as usize) - ); + let help_title = format!(" Help ─{}─ Esc to close ", "─".repeat(repeat_num as usize)); + + if app_state.is_force_redraw { + // We must also recalculate how many lines are wrapping to properly get scrolling to work on + // small terminal sizes... oh joy. + + // TODO: Make this more automated and easier to add. + + let mut overflow_buffer = 0; + let paragraph_width = draw_loc.width - 2; + constants::HELP_CONTENTS_TEXT.iter().for_each(|text_line| { + overflow_buffer += + UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16 / paragraph_width; + }); + + // General + app_state.help_dialog_state.general_index = + constants::HELP_CONTENTS_TEXT.len() as u16 + 1 + overflow_buffer; + constants::GENERAL_HELP_TEXT.iter().for_each(|text_line| { + overflow_buffer += + UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16 / paragraph_width; + }); + + // CPU + app_state.help_dialog_state.cpu_index = + (constants::HELP_CONTENTS_TEXT.len() + constants::GENERAL_HELP_TEXT.len()) as u16 + + 2 + + overflow_buffer; + constants::CPU_HELP_TEXT.iter().for_each(|text_line| { + overflow_buffer += + UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16 / paragraph_width; + }); + + // Processes + app_state.help_dialog_state.process_index = (constants::HELP_CONTENTS_TEXT.len() + + constants::GENERAL_HELP_TEXT.len() + + constants::CPU_HELP_TEXT.len()) + as u16 + + 3 + + overflow_buffer; + constants::PROCESS_HELP_TEXT.iter().for_each(|text_line| { + overflow_buffer += + UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16 / paragraph_width; + }); + + // Search + app_state.help_dialog_state.search_index = (constants::HELP_CONTENTS_TEXT.len() + + constants::GENERAL_HELP_TEXT.len() + + constants::CPU_HELP_TEXT.len() + + constants::PROCESS_HELP_TEXT.len()) + as u16 + + 4 + + overflow_buffer; + constants::SEARCH_HELP_TEXT.iter().for_each(|text_line| { + overflow_buffer += + UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16 / paragraph_width; + }); + + // Battery + app_state.help_dialog_state.battery_index = (constants::HELP_CONTENTS_TEXT.len() + + constants::GENERAL_HELP_TEXT.len() + + constants::CPU_HELP_TEXT.len() + + constants::PROCESS_HELP_TEXT.len() + + constants::SEARCH_HELP_TEXT.len()) + as u16 + + 5 + + overflow_buffer; + constants::BATTERY_HELP_TEXT.iter().for_each(|text_line| { + overflow_buffer += + UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16 / paragraph_width; + }); + + app_state.help_dialog_state.scroll_state.max_scroll_index = + (self.styled_help_text.len() as u16 + + (constants::NUM_CATEGORIES - 3) + + overflow_buffer) + .saturating_sub(draw_loc.height); + + // Fix if over-scrolled + if app_state + .help_dialog_state + .scroll_state + .current_scroll_index + >= app_state.help_dialog_state.scroll_state.max_scroll_index + { + app_state + .help_dialog_state + .scroll_state + .current_scroll_index = app_state + .help_dialog_state + .scroll_state + .max_scroll_index + .saturating_sub(1); + } + } f.render_widget( - Paragraph::new( - match app_state.help_dialog_state.current_category { - AppHelpCategory::General => &self.styled_general_help_text, - AppHelpCategory::Process => &self.styled_process_help_text, - AppHelpCategory::Search => &self.styled_search_help_text, - } - .iter(), - ) - .block( - Block::default() - .title(&help_title) - .title_style(self.colours.border_style) - .style(self.colours.border_style) - .borders(Borders::ALL) - .border_style(self.colours.border_style), - ) - .style(self.colours.text_style) - .alignment(Alignment::Left) - .wrap(true), + Paragraph::new(self.styled_help_text.iter()) + .block( + Block::default() + .title(&help_title) + .title_style(self.colours.border_style) + .style(self.colours.border_style) + .borders(Borders::ALL) + .border_style(self.colours.border_style), + ) + .style(self.colours.text_style) + .alignment(Alignment::Left) + .wrap(true) + .scroll( + app_state + .help_dialog_state + .scroll_state + .current_scroll_index, + ), draw_loc, ); } diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs index 6c008724..2281e503 100644 --- a/src/canvas/drawing_utils.rs +++ b/src/canvas/drawing_utils.rs @@ -1,6 +1,7 @@ use crate::app; use itertools::izip; +// TODO: Reverse intrinsic? /// A somewhat jury-rigged solution to simulate a variable intrinsic layout for /// table widths. Note that this will do one main pass to try to properly /// allocate widths. This will thus potentially cut off latter elements @@ -77,9 +78,9 @@ pub fn get_variable_intrinsic_widths( pub fn get_search_start_position( num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize, - current_cursor_position: usize, is_resized: bool, + current_cursor_position: usize, is_force_redraw: bool, ) -> usize { - if is_resized { + if is_force_redraw { *cursor_bar = 0; } @@ -117,9 +118,9 @@ pub fn get_search_start_position( pub fn get_start_position( num_rows: u64, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut u64, - currently_selected_position: u64, is_resized: bool, + currently_selected_position: u64, is_force_redraw: bool, ) -> u64 { - if is_resized { + if is_force_redraw { *scroll_position_bar = 0; } diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index cefed8a2..a49bd6ad 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -224,7 +224,7 @@ impl CpuGraphWidget for Painter { &cpu_widget_state.scroll_state.scroll_direction, &mut cpu_widget_state.scroll_state.previous_scroll_position, cpu_widget_state.scroll_state.current_scroll_position, - app_state.is_resized, + app_state.is_force_redraw, ); let is_on_widget = widget_id == app_state.current_widget.widget_id; diff --git a/src/canvas/widgets/disk_table.rs b/src/canvas/widgets/disk_table.rs index 13d24358..fda54d97 100644 --- a/src/canvas/widgets/disk_table.rs +++ b/src/canvas/widgets/disk_table.rs @@ -45,7 +45,7 @@ impl DiskTableWidget for Painter { &disk_widget_state.scroll_state.scroll_direction, &mut disk_widget_state.scroll_state.previous_scroll_position, disk_widget_state.scroll_state.current_scroll_position, - app_state.is_resized, + app_state.is_force_redraw, ); let is_on_widget = app_state.current_widget.widget_id == widget_id; let disk_table_state = &mut disk_widget_state.scroll_state.table_state; diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 0733b500..40803f9b 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -91,7 +91,7 @@ impl ProcessTableWidget for Painter { &proc_widget_state.scroll_state.scroll_direction, &mut proc_widget_state.scroll_state.previous_scroll_position, proc_widget_state.scroll_state.current_scroll_position, - app_state.is_resized, + app_state.is_force_redraw, ); // Sanity check @@ -370,7 +370,7 @@ impl ProcessTableWidget for Painter { .search_state .cursor_bar, current_cursor_position, - app_state.is_resized, + app_state.is_force_redraw, ); let query = proc_widget_state.get_current_search_query().as_str(); diff --git a/src/canvas/widgets/temp_table.rs b/src/canvas/widgets/temp_table.rs index 007d93b0..1e679904 100644 --- a/src/canvas/widgets/temp_table.rs +++ b/src/canvas/widgets/temp_table.rs @@ -46,7 +46,7 @@ impl TempTableWidget for Painter { &temp_widget_state.scroll_state.scroll_direction, &mut temp_widget_state.scroll_state.previous_scroll_position, temp_widget_state.scroll_state.current_scroll_position, - app_state.is_resized, + app_state.is_force_redraw, ); let is_on_widget = widget_id == app_state.current_widget.widget_id; let temp_table_state = &mut temp_widget_state.scroll_state.table_state; diff --git a/src/constants.rs b/src/constants.rs index a94dcc78..dca06781 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -36,52 +36,76 @@ lazy_static! { } // Help text +pub const NUM_CATEGORIES: u16 = 6; + +pub const HELP_CONTENTS_TEXT: [&str; 6] = [ + "Press the corresponding numbers to jump to the section, or scroll:\n", + "1 - General bindings\n", + "2 - CPU bindings\n", + "3 - Process bindings\n", + "4 - Process search bindings\n", + "5 - Battery bindings", +]; + pub const GENERAL_HELP_TEXT: [&str; 18] = [ - "General Keybindings\n\n", - "q, Ctrl-c Quit bottom\n", - "Esc Close filters, dialog boxes, etc.\n", - "Ctrl-r Reset all data\n", - "f Freeze display\n", - "Ctrl-Arrow Change your selected widget\n", - "Shift-Arrow Change your selected widget\n", - "H/J/K/L Change your selected widget up/down/left/right\n", - "Up, k Move cursor up\n", - "Down, j Move cursor down\n", - "? Open the help screen\n", - "gg Skip to the first entry of a list\n", - "G Skip to the last entry of a list\n", + "1 - General bindings\n", + "q, Ctrl-c Quit\n", + "Esc Close dialog windows, search, widgets, or exit maximized mode\n", + "Ctrl-r Reset display and any collected data\n", + "f Freeze/unfreeze updating with new data\n", + "Ctrl-Arrow \n", + "Shift-Arrow Move to a different widget\n", + "H/J/K/L \n", + "Up, k Scroll up\n", + "Down, j Scroll down\n", + "? Open help menu\n", + "gg Jump to the first entry\n", + "G Jump to the last entry\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", + "+ Zoom in on chart (decrease time range)\n", + "- Zoom out on chart (increase time range)\n", "= Reset zoom\n", + "Mouse scroll Scroll through the tables or zoom in/out of charts by scrolling up/down", +]; + +pub const CPU_HELP_TEXT: [&str; 4] = [ + "2 - CPU bindings\n", + "/ Open filtering for showing certain CPU cores\n", + "Space Toggle enabled/disabled cores\n", + "Esc Exit filtering mode", ]; pub const PROCESS_HELP_TEXT: [&str; 8] = [ - "Process Keybindings\n\n", - "dd, Delete Kill the highlighted process\n", - "c Sort by CPU usage\n", + "3 - Process bindings\n", + "dd Kill the selected process\n", + "c Sort by memory usage, press again to reverse sorting order\n", "m Sort by memory usage\n", - "p Sort by PID\n", - "n Sort by process name\n", - "Tab Group together processes with the same name\n", - "Ctrl-f, / Open up the search widget\n", + "p Sort by PID name, press again to reverse sorting order\n", + "n Sort by process name, press again to reverse sorting order\n", + "Tab Group/un-group processes with the same name\n", + "Ctrl-f, / Open process search widget", ]; pub const SEARCH_HELP_TEXT: [&str; 13] = [ - "Search Keybindings\n\n", - "Tab Toggle between searching for PID and name.\n", - "Esc Close search widget\n", - "Ctrl-a Skip to the start of search widget\n", - "Ctrl-e Skip to the end of search widget\n", + "4 - Process search bindings\n", + "Tab Toggle between searching for PID and name\n", + "Esc Close the search widget (retains the filter)\n", + "Ctrl-a Skip to the start of the search query\n", + "Ctrl-e Skip to the end of the search query\n", "Ctrl-u Clear the current search query\n", "Backspace Delete the character behind the cursor\n", "Delete Delete the character at the cursor\n", + "Alt-c/F1 Toggle matching case\n", + "Alt-w/F2 Toggle matching the entire word\n", + "Alt-r/F3 Toggle using regex\n", "Left Move cursor left\n", - "Right Move cursor right\n", - "Alt-c/F1 Toggle whether to ignore case\n", - "Alt-w/F2 Toggle whether to match the whole word\n", - "Alt-r/F3 Toggle whether to use regex\n", + "Right Move cursor right", +]; + +pub const BATTERY_HELP_TEXT: [&str; 3] = [ + "5 - Battery bindings\n", + "Left Go to previous battery\n", + "Right Go to next battery", ]; // Config and flags diff --git a/src/main.rs b/src/main.rs index 675e8cdf..1940a544 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,17 +114,6 @@ fn main() -> error::Result<()> { painter.colours.generate_remaining_cpu_colours(); painter.complete_painter_init(); - // Set up up tui and crossterm - let mut stdout_val = stdout(); - execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?; - enable_raw_mode()?; - - let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?; - terminal.hide_cursor()?; - - // Set panic hook - panic::set_hook(Box::new(|info| panic_hook(info))); - // Set up input handling let (tx, rx) = mpsc::channel(); create_input_thread(tx.clone()); @@ -151,6 +140,17 @@ fn main() -> error::Result<()> { app.used_widgets.clone(), ); + // Set up up tui and crossterm + let mut stdout_val = stdout(); + execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?; + enable_raw_mode()?; + + let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?; + terminal.hide_cursor()?; + + // Set panic hook + panic::set_hook(Box::new(|info| panic_hook(info))); + let mut first_run = true; loop { if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {