feature: Add scroll indicator to keep track of table position in widgets. (#333)

Adds the option to enable an "out of" indicator for scrollable table widgets (using --show_table_scroll_position).
This commit is contained in:
Clement Tsang 2020-11-28 15:37:06 -05:00 committed by GitHub
parent a9c1197075
commit 3260ff4663
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 214 additions and 55 deletions

View file

@ -203,6 +203,8 @@ jobs:
run: |
strip target/${{ matrix.triple.target }}/release/btm
# TODO: Strip ARM
- name: Bundle release and completion (Windows)
if: matrix.triple.os == 'windows-2019'
shell: bash

View file

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.6.0] - Unreleased
## Features
- [#333](https://github.com/ClementTsang/bottom/pull/333): Adds an "out of" indicator that can be enabled using `--show_table_scroll_position` to help keep track of scrolled position.
## Changes
## Bug Fixes
## [0.5.3] - 2020-11-26
## Bug Fixes

View file

@ -238,6 +238,7 @@ Use `btm --help` for more information.
--mem_as_value Defaults to showing process memory usage by value.
-r, --rate <MS> Sets a refresh rate in ms.
-R, --regex Enables regex by default.
--show_table_scroll_position Shows the scroll position tracker in table widgets
-d, --time_delta <MS> The amount in ms changed upon zooming.
-T, --tree Defaults to showing the process widget in tree mode.
--use_old_network_legend DEPRECATED - uses the older network legend.
@ -528,31 +529,32 @@ The following options can be set under `[flags]` to achieve the same effect as p
These are the following supported flag config values, which correspond to the flag of the same name described in [Flags](#flags):
| Field | Type |
| ------------------------ | ------------------------------------------------------------------------------------- |
| `hide_avg_cpu` | Boolean |
| `dot_marker` | Boolean |
| `left_legend` | Boolean |
| `current_usage` | Boolean |
| `group_processes` | Boolean |
| `case_sensitive` | Boolean |
| `whole_word` | Boolean |
| `regex` | Boolean |
| `show_disabled_data` | Boolean |
| `basic` | Boolean |
| `hide_table_count` | Boolean |
| `use_old_network_legend` | Boolean |
| `battery` | Boolean |
| `rate` | Unsigned Int (represents milliseconds) |
| `default_time_value` | Unsigned Int (represents milliseconds) |
| `time_delta` | Unsigned Int (represents milliseconds) |
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) |
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) |
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) |
| `disable_click` | Boolean |
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light"]) |
| `mem_as_value` | Boolean |
| `tree` | Boolean |
| Field | Type |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| `hide_avg_cpu` | Boolean |
| `dot_marker` | Boolean |
| `left_legend` | Boolean |
| `current_usage` | Boolean |
| `group_processes` | Boolean |
| `case_sensitive` | Boolean |
| `whole_word` | Boolean |
| `regex` | Boolean |
| `show_disabled_data` | Boolean |
| `basic` | Boolean |
| `hide_table_count` | Boolean |
| `use_old_network_legend` | Boolean |
| `battery` | Boolean |
| `rate` | Unsigned Int (represents milliseconds) |
| `default_time_value` | Unsigned Int (represents milliseconds) |
| `time_delta` | Unsigned Int (represents milliseconds) |
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) |
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) |
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) |
| `disable_click` | Boolean |
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light"]) |
| `mem_as_value` | Boolean |
| `tree` | Boolean |
| `show_table_scroll_position` | Boolean |
#### Theming

View file

@ -48,6 +48,7 @@ pub struct AppConfigFields {
pub table_gap: u16,
pub disable_click: bool,
pub no_write: bool,
pub show_table_scroll_position: bool,
}
/// For filtering out information

View file

@ -868,11 +868,11 @@ pub struct BottomWidget {
#[builder(default = None)]
pub parent_reflector: Option<(WidgetDirection, u64)>,
/// Top left corner when drawn, for mouse click detection
/// Top left corner when drawn, for mouse click detection. (x, y)
#[builder(default = None)]
pub top_left_corner: Option<(u16, u16)>,
/// Bottom right corner when drawn, for mouse click detection
/// Bottom right corner when drawn, for mouse click detection. (x, y)
#[builder(default = None)]
pub bottom_right_corner: Option<(u16, u16)>,
}

View file

@ -42,7 +42,6 @@ impl DiskTableWidget for Painter {
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
let disk_data: &mut [Vec<String>] = &mut app_state.canvas_data.disk_data;
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
@ -65,7 +64,7 @@ impl DiskTableWidget for Painter {
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &disk_data[start_position..];
let sliced_vec = &app_state.canvas_data.disk_data[start_position..];
// Calculate widths
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
@ -154,7 +153,6 @@ impl DiskTableWidget for Painter {
Row::Data(truncated_data)
});
// TODO: This seems to be bugged? The selected text style gets "stuck"? I think this gets fixed with tui 0.10?
let (border_style, highlight_style) = if is_on_widget {
(
self.colours.highlighted_border_style,
@ -164,22 +162,62 @@ impl DiskTableWidget for Painter {
(self.colours.border_style, self.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Disk ({} of {}) ",
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.disk_data.len()
);
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Disk ".to_string()
}
} else {
" Disk ".to_string()
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Disk ── Esc to go back ";
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Disk ".to_string(),
format!("{}{}", " Disk ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(" Disk ", self.colours.widget_title_style),
Span::styled(chosen_title_base, self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(
expanded_title_base.as_str(),
true
)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(" Disk ", self.colours.widget_title_style))
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
};
let disk_block = if draw_border {

View file

@ -207,6 +207,33 @@ impl ProcessTableWidget for Painter {
(self.colours.border_style, self.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
if let Some(finalized_process_data) = app_state
.canvas_data
.finalized_process_data_map
.get(&widget_id)
{
let title = format!(
" Processes ({} of {}) ",
proc_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
finalized_process_data.len()
);
if title.len() <= draw_loc.width as usize {
title
} else {
" Processes ".to_string()
}
} else {
" Processes ".to_string()
}
} else {
" Processes ".to_string()
};
let title = if app_state.is_expanded
&& !proc_widget_state
.process_search_state
@ -214,21 +241,42 @@ impl ProcessTableWidget for Painter {
.is_enabled
&& !proc_widget_state.is_sort_open
{
const TITLE_BASE: &str = " Processes ── Esc to go back ";
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Processes ".to_string(),
format!("{}{}", " Processes ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(" Processes ", self.colours.widget_title_style),
Span::styled(chosen_title_base, self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(
expanded_title_base.as_str(),
true
)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(" Processes ", self.colours.widget_title_style))
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
};
let process_block = if draw_border {
@ -281,6 +329,7 @@ impl ProcessTableWidget for Painter {
disabled,
)
});
let proc_table_state = &mut proc_widget_state.scroll_state.table_state;
proc_table_state.select(Some(
proc_widget_state
@ -434,7 +483,6 @@ impl ProcessTableWidget for Painter {
}
});
// FIXME: gotop's "x out of y" thing is really nice to help keep track of the scroll position. Add to everything?
f.render_stateful_widget(
Table::new(process_headers.iter(), process_rows)
.block(process_block)

View file

@ -42,8 +42,6 @@ impl TempTableWidget for Painter {
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
let temp_sensor_data: &mut [Vec<String>] = &mut app_state.canvas_data.temp_sensor_data;
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
@ -66,7 +64,7 @@ impl TempTableWidget for Painter {
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &temp_sensor_data[start_position..];
let sliced_vec = &app_state.canvas_data.temp_sensor_data[start_position..];
// Calculate widths
let hard_widths = [None, None];
@ -153,25 +151,62 @@ impl TempTableWidget for Painter {
(self.colours.border_style, self.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Temperatures ({} of {}) ",
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.temp_sensor_data.len()
);
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Temperatures ".to_string()
}
} else {
" Temperatures ".to_string()
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Temperatures ── Esc to go back ";
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Temperatures ".to_string(),
format!("{}{}", " Temperatures ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(" Temperatures ", self.colours.widget_title_style),
Span::styled(chosen_title_base, self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(
expanded_title_base.as_str(),
true
)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(
" Temperatures ",
self.colours.widget_title_style,
))
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
};
let temp_block = if draw_border {

View file

@ -135,6 +135,13 @@ Hides the spacing between table headers and entries.\n\n",
"\
Completely hides the time scaling from being shown.\n\n",
);
let show_table_scroll_position = Arg::with_name("show_table_scroll_position")
.long("show_table_scroll_position")
.help("Shows the scroll position tracker in table widgets")
.long_help(
"\
Shows the list scroll position tracker in the widget title for table widgets.\n\n",
);
let left_legend = Arg::with_name("left_legend")
.short("l")
.long("left_legend")
@ -366,6 +373,7 @@ Defaults to showing the process widget in tree mode.\n\n",
.arg(hide_avg_cpu)
.arg(hide_table_gap)
.arg(hide_time)
.arg(show_table_scroll_position)
.arg(left_legend)
// .arg(no_write)
.arg(rate)

View file

@ -147,6 +147,9 @@ pub struct ConfigFlags {
#[builder(default, setter(strip_option))]
pub tree: Option<bool>,
#[builder(default, setter(strip_option))]
show_table_scroll_position: Option<bool>,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
@ -388,6 +391,7 @@ pub fn build_app(
disable_click: get_disable_click(matches, config),
// no_write: get_no_write(matches, config),
no_write: false,
show_table_scroll_position: get_show_table_scroll_position(matches, config),
};
let used_widgets = UsedWidgets {
@ -972,3 +976,14 @@ fn get_is_default_tree(matches: &clap::ArgMatches<'static>, config: &Config) ->
}
false
}
fn get_show_table_scroll_position(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("show_table_scroll_position") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(show_table_scroll_position) = flags.show_table_scroll_position {
return show_table_scroll_position;
}
}
false
}