From 9eabb061aaad8a1ebf6545dc8f36c98c1e622774 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Mon, 27 Dec 2021 15:23:11 -0800 Subject: [PATCH] feature: add basic page up/down scrolling (#646) Adds page up/down scrolling support to respectively scroll up/down by a full page. Note that this is mostly just to get the feature out for those interested, and is admittedly a bit rushed - I will be rewriting all logic involving event handling as part of state refactor anyways, so this will also get changed in the work done there, and therefore, I kinda just sped through this. --- CHANGELOG.md | 4 + docs/content/usage/general-usage.md | 1 + src/app.rs | 113 +++++++++++++++++----------- src/canvas.rs | 7 ++ src/constants.rs | 3 +- src/lib.rs | 2 + 6 files changed, 86 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cdaceae..575cb397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.6.7]/[0.7.0] - Unreleased +## Feature + +- [#646](https://github.com/ClementTsang/bottom/pull/646): Add `PgUp`/`PgDown` keybind support to scroll up and down a page in a table. + ## [0.6.6] - 2021-12-22 ## Bug Fixes diff --git a/docs/content/usage/general-usage.md b/docs/content/usage/general-usage.md index 4bfcdb61..ea1ed104 100644 --- a/docs/content/usage/general-usage.md +++ b/docs/content/usage/general-usage.md @@ -55,6 +55,7 @@ Note that key bindings are generally case-sensitive. | ++right++
++l++
++alt+l++ | Move right within a widget | | ++g+g++ , ++home++ | Jump to the first entry | | ++G++ , ++end++ | Jump to the last entry | +| ++page-up++ , ++page-down++ | Scroll up/down a table by a page | ## Mouse bindings diff --git a/src/app.rs b/src/app.rs index b6bca61d..22a467ee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1114,6 +1114,20 @@ impl App { 0 => KillSignal::Cancel, sig => KillSignal::Kill(sig), }; + } else if self.current_widget.widget_type.is_widget_table() { + if let (Some((_tlc_x, tlc_y)), Some((_brc_x, brc_y))) = ( + &self.current_widget.top_left_corner, + &self.current_widget.bottom_right_corner, + ) { + let border_offset = if self.is_drawing_border() { 1 } else { 0 }; + let header_gap_offset = 1 + if self.is_drawing_gap(&self.current_widget) { + self.app_config_fields.table_gap + } else { + 0 + }; + let height = brc_y - tlc_y - 2 * border_offset - header_gap_offset; + self.change_position_count(-(height as i64)); + } } } @@ -1127,6 +1141,20 @@ impl App { new_signal += 2; } self.delete_dialog_state.selected_signal = KillSignal::Kill(new_signal); + } else if self.current_widget.widget_type.is_widget_table() { + if let (Some((_tlc_x, tlc_y)), Some((_brc_x, brc_y))) = ( + &self.current_widget.top_left_corner, + &self.current_widget.bottom_right_corner, + ) { + let border_offset = if self.is_drawing_border() { 1 } else { 0 }; + let header_gap_offset = 1 + if self.is_drawing_gap(&self.current_widget) { + self.app_config_fields.table_gap + } else { + 0 + }; + let height = brc_y - tlc_y - 2 * border_offset - header_gap_offset; + self.change_position_count(height as i64); + } } } @@ -1443,8 +1471,6 @@ impl App { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { self.on_number(caught_char) } - 'u' => self.on_page_up(), - 'd' => self.on_page_down(), 'g' => { let mut is_first_g = true; if let Some(second_char) = self.second_char { @@ -2330,36 +2356,29 @@ impl App { } pub fn decrement_position_count(&mut self) { - if !self.ignore_normal_keybinds() { - match self.current_widget.widget_type { - BottomWidgetType::Proc => { - self.increment_process_position(-1); - } - BottomWidgetType::ProcSort => self.increment_process_sort_position(-1), - BottomWidgetType::Temp => self.increment_temp_position(-1), - BottomWidgetType::Disk => self.increment_disk_position(-1), - BottomWidgetType::CpuLegend => self.increment_cpu_legend_position(-1), - _ => {} - } - } + self.change_position_count(-1); } pub fn increment_position_count(&mut self) { + self.change_position_count(1); + } + + fn change_position_count(&mut self, amount: i64) { if !self.ignore_normal_keybinds() { match self.current_widget.widget_type { BottomWidgetType::Proc => { - self.increment_process_position(1); + self.change_process_position(amount); } - BottomWidgetType::ProcSort => self.increment_process_sort_position(1), - BottomWidgetType::Temp => self.increment_temp_position(1), - BottomWidgetType::Disk => self.increment_disk_position(1), - BottomWidgetType::CpuLegend => self.increment_cpu_legend_position(1), + BottomWidgetType::ProcSort => self.change_process_sort_position(amount), + BottomWidgetType::Temp => self.change_temp_position(amount), + BottomWidgetType::Disk => self.increment_disk_position(amount), + BottomWidgetType::CpuLegend => self.change_cpu_legend_position(amount), _ => {} } } } - fn increment_process_sort_position(&mut self, num_to_change_by: i64) { + fn change_process_sort_position(&mut self, num_to_change_by: i64) { if let Some(proc_widget_state) = self .proc_state .get_mut_widget_state(self.current_widget.widget_id - 2) @@ -2367,9 +2386,11 @@ impl App { let current_posn = proc_widget_state.columns.current_scroll_position; let num_columns = proc_widget_state.columns.get_enabled_columns_len(); - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by < num_columns as i64 - { + if current_posn as i64 + num_to_change_by < 0 { + proc_widget_state.columns.current_scroll_position = 0; + } else if current_posn as i64 + num_to_change_by >= num_columns as i64 { + proc_widget_state.columns.current_scroll_position = num_columns.saturating_sub(1); + } else { proc_widget_state.columns.current_scroll_position = (current_posn as i64 + num_to_change_by) as usize; } @@ -2382,7 +2403,7 @@ impl App { } } - fn increment_cpu_legend_position(&mut self, num_to_change_by: i64) { + fn change_cpu_legend_position(&mut self, num_to_change_by: i64) { if let Some(cpu_widget_state) = self .cpu_state .widget_states @@ -2391,9 +2412,11 @@ impl App { let current_posn = cpu_widget_state.scroll_state.current_scroll_position; let cap = self.canvas_data.cpu_data.len(); - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by < cap as i64 - { + if current_posn as i64 + num_to_change_by < 0 { + cpu_widget_state.scroll_state.current_scroll_position = 0; + } else if current_posn as i64 + num_to_change_by >= cap as i64 { + cpu_widget_state.scroll_state.current_scroll_position = cap.saturating_sub(1); + } else { cpu_widget_state.scroll_state.current_scroll_position = (current_posn as i64 + num_to_change_by) as usize; } @@ -2407,7 +2430,7 @@ impl App { } /// Returns the new position. - fn increment_process_position(&mut self, num_to_change_by: i64) -> Option { + fn change_process_position(&mut self, num_to_change_by: i64) -> Option { if let Some(proc_widget_state) = self .proc_state .get_mut_widget_state(self.current_widget.widget_id) @@ -2418,13 +2441,16 @@ impl App { .finalized_process_data_map .get(&self.current_widget.widget_id) { - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by < finalized_process_data.len() as i64 + if current_posn as i64 + num_to_change_by < 0 { + proc_widget_state.scroll_state.current_scroll_position = 0; + } else if current_posn as i64 + num_to_change_by + >= finalized_process_data.len() as i64 { proc_widget_state.scroll_state.current_scroll_position = - (current_posn as i64 + num_to_change_by) as usize; + finalized_process_data.len().saturating_sub(1); } else { - return None; + proc_widget_state.scroll_state.current_scroll_position = + (current_posn as i64 + num_to_change_by) as usize; } } @@ -2440,7 +2466,7 @@ impl App { None } - fn increment_temp_position(&mut self, num_to_change_by: i64) { + fn change_temp_position(&mut self, num_to_change_by: i64) { if let Some(temp_widget_state) = self .temp_state .widget_states @@ -2448,10 +2474,14 @@ impl App { { let current_posn = temp_widget_state.scroll_state.current_scroll_position; - if current_posn as i64 + num_to_change_by >= 0 - && current_posn as i64 + num_to_change_by - < self.canvas_data.temp_sensor_data.len() as i64 + if current_posn as i64 + num_to_change_by < 0 { + temp_widget_state.scroll_state.current_scroll_position = 0; + } else if current_posn as i64 + num_to_change_by + >= self.canvas_data.temp_sensor_data.len() as i64 { + temp_widget_state.scroll_state.current_scroll_position = + self.canvas_data.temp_sensor_data.len().saturating_sub(1); + } else { temp_widget_state.scroll_state.current_scroll_position = (current_posn as i64 + num_to_change_by) as usize; } @@ -2901,9 +2931,6 @@ impl App { } let mut failed_to_get = true; - // TODO: [MOUSE] We could use a better data structure for this? Currently it's a blind - // traversal through a hashmap, using a 2d binary tree of sorts would be better. - // See: https://docs.rs/kdtree/0.6.0/kdtree/ for (new_widget_id, widget) in &self.widget_map { if let (Some((tlc_x, tlc_y)), Some((brc_x, brc_y))) = (widget.top_left_corner, widget.bottom_right_corner) @@ -2985,7 +3012,7 @@ impl App { .current_scroll_position; let is_tree_mode = proc_widget_state.is_tree_mode; - let new_position = self.increment_process_position( + let new_position = self.change_process_position( offset_clicked_entry as i64 - visual_index as i64, ); @@ -3008,7 +3035,7 @@ impl App { if let Some(visual_index) = proc_widget_state.columns.column_state.selected() { - self.increment_process_sort_position( + self.change_process_sort_position( offset_clicked_entry as i64 - visual_index as i64, ); } @@ -3022,7 +3049,7 @@ impl App { if let Some(visual_index) = cpu_widget_state.scroll_state.table_state.selected() { - self.increment_cpu_legend_position( + self.change_cpu_legend_position( offset_clicked_entry as i64 - visual_index as i64, ); } @@ -3036,7 +3063,7 @@ impl App { if let Some(visual_index) = temp_widget_state.scroll_state.table_state.selected() { - self.increment_temp_position( + self.change_temp_position( offset_clicked_entry as i64 - visual_index as i64, ); } diff --git a/src/canvas.rs b/src/canvas.rs index c8f62640..f19481e0 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -695,6 +695,13 @@ impl Painter { } })?; + if let Some(updated_current_widget) = app_state + .widget_map + .get(&app_state.current_widget.widget_id) + { + app_state.current_widget = updated_current_widget.clone(); + } + app_state.is_force_redraw = false; app_state.is_determining_widget_boundary = false; diff --git a/src/constants.rs b/src/constants.rs index 4464c16f..48e115f7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -231,7 +231,7 @@ pub const HELP_CONTENTS_TEXT: [&str; 8] = [ // TODO [Help]: Search in help? // TODO [Help]: Move to using tables for easier formatting? -pub const GENERAL_HELP_TEXT: [&str; 30] = [ +pub const GENERAL_HELP_TEXT: [&str; 31] = [ "1 - General", "q, Ctrl-c Quit", "Esc Close dialog windows, search, widgets, or exit expanded mode", @@ -260,6 +260,7 @@ pub const GENERAL_HELP_TEXT: [&str; 30] = [ "+ Zoom in on chart (decrease time range)", "- Zoom out on chart (increase time range)", "= Reset zoom", + "PgUp, PgDown Scroll up/down a table by a page", "Mouse scroll Scroll through the tables or zoom in/out of charts by scrolling up/down", "Mouse click Selects the clicked widget, table entry, dialog option, or tab", ]; diff --git a/src/lib.rs b/src/lib.rs index 71f6c078..4de735d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,6 +125,8 @@ pub fn handle_key_event_or_break( KeyCode::F(5) => app.toggle_tree_mode(), KeyCode::F(6) => app.toggle_sort(), KeyCode::F(9) => app.start_killing_process(), + KeyCode::PageDown => app.on_page_down(), + KeyCode::PageUp => app.on_page_up(), _ => {} } } else {