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 {