feature: Add option to set a position of legend (#1430)

* Add option to set a position of legend

* some small changes

---------

Co-authored-by: ClementTsang <34804052+ClementTsang@users.noreply.github.com>
This commit is contained in:
Lee Wonjoon 2024-04-02 05:06:01 +00:00 committed by GitHub
parent 2ee0df1502
commit a083ec00dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 169 additions and 41 deletions

View file

@ -58,6 +58,7 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
| ----------------------- | --------------------------------------------------------- | | ----------------------- | --------------------------------------------------------- |
| `--enable_cache_memory` | Enable collecting and displaying cache and buffer memory. | | `--enable_cache_memory` | Enable collecting and displaying cache and buffer memory. |
| `--mem_as_value` | Defaults to showing process memory usage by value. | | `--mem_as_value` | Defaults to showing process memory usage by value. |
| `--memory_legend` | Where to place the legend for the memory widget. |
## Network Options ## Network Options
@ -66,6 +67,7 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
| `--network_use_binary_prefix` | Displays the network widget with binary prefixes. | | `--network_use_binary_prefix` | Displays the network widget with binary prefixes. |
| `--network_use_bytes` | Displays the network widget using bytes. | | `--network_use_bytes` | Displays the network widget using bytes. |
| `--network_use_log` | Displays the network widget with a log scale. | | `--network_use_log` | Displays the network widget with a log scale. |
| `--network_legend` | Where to place the legend for the network widget. |
| `--use_old_network_legend` | DEPRECATED - uses a separate network legend. | | `--use_old_network_legend` | DEPRECATED - uses a separate network legend. |
## Battery Options ## Battery Options

View file

@ -7,38 +7,40 @@
Most of the [command line flags](../command-line-options.md) have config file equivalents to avoid having to type them out Most of the [command line flags](../command-line-options.md) have config file equivalents to avoid having to type them out
each time: each time:
| Field | Type | Functionality | | Field | Type | Functionality |
| ---------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| `hide_avg_cpu` | Boolean | Hides the average CPU usage. | | `hide_avg_cpu` | Boolean | Hides the average CPU usage. |
| `dot_marker` | Boolean | Uses a dot marker for graphs. | | `dot_marker` | Boolean | Uses a dot marker for graphs. |
| `left_legend` | Boolean | Puts the CPU chart legend to the left side. | | `left_legend` | Boolean | Puts the CPU chart legend to the left side. |
| `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. | | `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. |
| `group_processes` | Boolean | Groups processes with the same name by default. | | `group_processes` | Boolean | Groups processes with the same name by default. |
| `case_sensitive` | Boolean | Enables case sensitivity by default. | | `case_sensitive` | Boolean | Enables case sensitivity by default. |
| `whole_word` | Boolean | Enables whole-word matching by default. | | `whole_word` | Boolean | Enables whole-word matching by default. |
| `regex` | Boolean | Enables regex by default. | | `regex` | Boolean | Enables regex by default. |
| `basic` | Boolean | Hides graphs and uses a more basic look. | | `basic` | Boolean | Hides graphs and uses a more basic look. |
| `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. | | `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. |
| `battery` | Boolean | Shows the battery widget. | | `battery` | Boolean | Shows the battery widget. |
| `rate` | Unsigned Int (represents milliseconds) or String (represents human time) | Sets a refresh rate in ms. | | `rate` | Unsigned Int (represents milliseconds) or String (represents human time) | Sets a refresh rate in ms. |
| `default_time_value` | Unsigned Int (represents milliseconds) or String (represents human time) | Default time value for graphs in ms. | | `default_time_value` | Unsigned Int (represents milliseconds) or String (represents human time) | Default time value for graphs in ms. |
| `time_delta` | Unsigned Int (represents milliseconds) or String (represents human time) | The amount in ms changed upon zooming. | | `time_delta` | Unsigned Int (represents milliseconds) or String (represents human time) | The amount in ms changed upon zooming. |
| `hide_time` | Boolean | Hides the time scale. | | `hide_time` | Boolean | Hides the time scale. |
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. | | `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. |
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. | | `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. |
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. | | `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. |
| `disable_click` | Boolean | Disables mouse clicks. | | `disable_click` | Boolean | Disables mouse clicks. |
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"]) | Use a color scheme, use --help for supported values. | | `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"]) | Use a color scheme, use --help for supported values. |
| `enable_cache_memory` | Boolean | Enable collecting and displaying cache and buffer memory (not available on Windows). | | `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). |
| `mem_as_value` | Boolean | Defaults to showing process memory usage by value. | | `mem_as_value` | Boolean | Defaults to showing process memory usage by value. |
| `tree` | Boolean | Defaults to showing the process widget in tree mode. | | `tree` | Boolean | Defaults to showing the process widget in tree mode. |
| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. | | `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. |
| `process_command` | Boolean | Show processes as their commands by default. | | `process_command` | Boolean | Show processes as their commands by default. |
| `disable_advanced_kill` | Boolean | Hides advanced options to stop a process on Unix-like systems. | | `disable_advanced_kill` | Boolean | Hides advanced options to stop a process on Unix-like systems. |
| `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. | | `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. |
| `network_use_bytes` | Boolean | Displays the network widget using bytes. | | `network_use_bytes` | Boolean | Displays the network widget using bytes. |
| `network_use_log` | Boolean | Displays the network widget with a log scale. | | `network_use_log` | Boolean | Displays the network widget with a log scale. |
| `enable_gpu` | Boolean | Shows the GPU widgets. | | `enable_gpu` | Boolean | Shows the GPU widgets. |
| `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. | | `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. |
| `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. | | `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. |
| `expanded_on_startup` | Boolean | Expand the default widget upon starting the app. | | `expanded_on_startup` | Boolean | Expand the default widget upon starting the app. |
| `memory_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the memory widget. |
| `network_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the network widget. |

View file

@ -21,6 +21,7 @@ pub use states::*;
use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
use crate::{ use crate::{
canvas::components::time_chart::LegendPosition,
constants, constants,
data_collection::temperature, data_collection::temperature,
data_conversion::ConvertedData, data_conversion::ConvertedData,
@ -62,8 +63,10 @@ pub struct AppConfigFields {
pub enable_cache_memory: bool, pub enable_cache_memory: bool,
pub show_table_scroll_position: bool, pub show_table_scroll_position: bool,
pub is_advanced_kill: bool, pub is_advanced_kill: bool,
pub memory_legend_position: Option<LegendPosition>,
// TODO: Remove these, move network details state-side. // TODO: Remove these, move network details state-side.
pub network_unit_type: DataUnit, pub network_unit_type: DataUnit,
pub network_legend_position: Option<LegendPosition>,
pub network_scale_type: AxisScaling, pub network_scale_type: AxisScaling,
pub network_use_binary_prefix: bool, pub network_use_binary_prefix: bool,
pub retention_ms: u64, pub retention_ms: u64,

View file

@ -11,7 +11,9 @@ use tui::{
}; };
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::time_chart::{Axis, Dataset, Point, TimeChart, DEFAULT_LEGEND_CONSTRAINTS}; use super::time_chart::{
Axis, Dataset, LegendPosition, Point, TimeChart, DEFAULT_LEGEND_CONSTRAINTS,
};
/// Represents the data required by the [`TimeGraph`]. /// Represents the data required by the [`TimeGraph`].
pub struct GraphData<'a> { pub struct GraphData<'a> {
@ -48,6 +50,9 @@ pub struct TimeGraph<'a> {
/// The title style. /// The title style.
pub title_style: Style, pub title_style: Style,
/// The legend position.
pub legend_position: Option<LegendPosition>,
/// Any legend constraints. /// Any legend constraints.
pub legend_constraints: Option<(Constraint, Constraint)>, pub legend_constraints: Option<(Constraint, Constraint)>,
@ -142,6 +147,7 @@ impl<'a> TimeGraph<'a> {
.y_axis(y_axis) .y_axis(y_axis)
.marker(self.marker) .marker(self.marker)
.legend_style(self.graph_style) .legend_style(self.graph_style)
.legend_position(self.legend_position)
.hidden_legend_constraints( .hidden_legend_constraints(
self.legend_constraints self.legend_constraints
.unwrap_or(DEFAULT_LEGEND_CONSTRAINTS), .unwrap_or(DEFAULT_LEGEND_CONSTRAINTS),
@ -202,6 +208,7 @@ mod test {
border_style: Style::default().fg(Color::Blue), border_style: Style::default().fg(Color::Blue),
is_expanded: false, is_expanded: false,
title_style: Style::default().fg(Color::Cyan), title_style: Style::default().fg(Color::Cyan),
legend_position: None,
legend_constraints: None, legend_constraints: None,
marker: Marker::Braille, marker: Marker::Braille,
} }

View file

@ -7,7 +7,7 @@
mod canvas; mod canvas;
mod points; mod points;
use std::cmp::max; use std::{cmp::max, str::FromStr};
use canvas::*; use canvas::*;
use tui::{ use tui::{
@ -213,6 +213,27 @@ impl LegendPosition {
} }
} }
#[derive(Debug, PartialEq)]
pub struct ParseLegendPositionError;
impl FromStr for LegendPosition {
type Err = ParseLegendPositionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"top" => Ok(Self::Top),
"top-left" => Ok(Self::TopLeft),
"top-right" => Ok(Self::TopRight),
"left" => Ok(Self::Left),
"right" => Ok(Self::Right),
"bottom-left" => Ok(Self::BottomLeft),
"bottom" => Ok(Self::Bottom),
"bottom-right" => Ok(Self::BottomRight),
_ => Err(ParseLegendPositionError),
}
}
}
/// A group of data points /// A group of data points
/// ///
/// This is the main element composing a [`TimeChart`]. /// This is the main element composing a [`TimeChart`].

View file

@ -233,6 +233,7 @@ impl Painter {
title, title,
is_expanded: app_state.is_expanded, is_expanded: app_state.is_expanded,
title_style: self.colours.widget_title_style, title_style: self.colours.widget_title_style,
legend_position: None,
legend_constraints: None, legend_constraints: None,
marker, marker,
} }

View file

@ -133,6 +133,7 @@ impl Painter {
title: " Memory ".into(), title: " Memory ".into(),
is_expanded: app_state.is_expanded, is_expanded: app_state.is_expanded,
title_style: self.colours.widget_title_style, title_style: self.colours.widget_title_style,
legend_position: app_state.app_config_fields.memory_legend_position,
legend_constraints: Some((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))), legend_constraints: Some((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))),
marker, marker,
} }

View file

@ -162,6 +162,7 @@ impl Painter {
title: " Network ".into(), title: " Network ".into(),
is_expanded: app_state.is_expanded, is_expanded: app_state.is_expanded,
title_style: self.colours.widget_title_style, title_style: self.colours.widget_title_style,
legend_position: app_state.app_config_fields.network_legend_position,
legend_constraints: Some(legend_constraints), legend_constraints: Some(legend_constraints),
marker, marker,
} }

View file

@ -573,7 +573,7 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
#battery = false #battery = false
# Disable mouse clicks # Disable mouse clicks
#disable_click = false #disable_click = false
# Built-in themes. Valid values are "default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light" # Built-in themes. Valid values are "default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"
#color = "default" #color = "default"
# Show memory values in the processes widget as values by default # Show memory values in the processes widget as values by default
#mem_as_value = false #mem_as_value = false
@ -597,6 +597,10 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
#enable_cache_memory = false #enable_cache_memory = false
# How much data is stored at once in terms of time. # How much data is stored at once in terms of time.
#retention = "10m" #retention = "10m"
# Where to place the legend for the memory widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
#memory_legend = "TopRight".
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
#network_legend = "TopRight".
# These are flags around the process widget. # These are flags around the process widget.
#[processes] #[processes]

View file

@ -24,7 +24,7 @@ use starship_battery::Manager;
use self::config::{layout::Row, IgnoreList, StringOrNum}; use self::config::{layout::Row, IgnoreList, StringOrNum};
use crate::{ use crate::{
app::{filter::Filter, layout_manager::*, *}, app::{filter::Filter, layout_manager::*, *},
canvas::{styling::CanvasStyling, ColourScheme}, canvas::{components::time_chart::LegendPosition, styling::CanvasStyling, ColourScheme},
constants::*, constants::*,
data_collection::temperature::TemperatureType, data_collection::temperature::TemperatureType,
utils::{ utils::{
@ -126,6 +126,9 @@ pub fn init_app(
} }
}; };
let network_legend_position = get_network_legend(matches, config)?;
let memory_legend_position = get_memory_legend(matches, config)?;
// TODO: Can probably just reuse the options struct. // TODO: Can probably just reuse the options struct.
let app_config_fields = AppConfigFields { let app_config_fields = AppConfigFields {
update_rate: get_update_rate(matches, config) update_rate: get_update_rate(matches, config)
@ -150,6 +153,8 @@ pub fn init_app(
enable_cache_memory: get_enable_cache_memory(matches, config), enable_cache_memory: get_enable_cache_memory(matches, config),
show_table_scroll_position: is_flag_enabled!(show_table_scroll_position, matches, config), show_table_scroll_position: is_flag_enabled!(show_table_scroll_position, matches, config),
is_advanced_kill, is_advanced_kill,
memory_legend_position,
network_legend_position,
network_scale_type, network_scale_type,
network_unit_type, network_unit_type,
network_use_binary_prefix, network_use_binary_prefix,
@ -772,6 +777,48 @@ fn get_retention(matches: &ArgMatches, config: &Config) -> error::Result<u64> {
} }
} }
fn get_network_legend(
matches: &ArgMatches, config: &Config,
) -> error::Result<Option<LegendPosition>> {
let error =
|_| BottomError::ConfigError("network_legend is set to an invalid value".to_string());
if let Some(s) = matches.get_one::<String>("network_legend") {
match s.to_ascii_lowercase().trim() {
"none" => Ok(None),
position => Ok(Some(position.parse::<LegendPosition>().map_err(error)?)),
}
} else if let Some(flags) = &config.flags {
if let Some(legend) = &flags.network_legend {
Ok(Some(legend.parse::<LegendPosition>().map_err(error)?))
} else {
Ok(Some(LegendPosition::default()))
}
} else {
Ok(Some(LegendPosition::default()))
}
}
fn get_memory_legend(
matches: &ArgMatches, config: &Config,
) -> error::Result<Option<LegendPosition>> {
let error =
|_| BottomError::ConfigError("memory_legend is set to an invalid value".to_string());
if let Some(s) = matches.get_one::<String>("memory_legend") {
match s.to_ascii_lowercase().trim() {
"none" => Ok(None),
position => Ok(Some(position.parse::<LegendPosition>().map_err(error)?)),
}
} else if let Some(flags) = &config.flags {
if let Some(legend) = &flags.memory_legend {
Ok(Some(legend.parse::<LegendPosition>().map_err(error)?))
} else {
Ok(Some(LegendPosition::default()))
}
} else {
Ok(Some(LegendPosition::default()))
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use clap::ArgMatches; use clap::ArgMatches;

View file

@ -436,6 +436,24 @@ fn mem_args(cmd: Command) -> Command {
to showing it by percentage.", to showing it by percentage.",
); );
let memory_legend = Arg::new("memory_legend")
.long("memory_legend")
.action(ArgAction::Set)
.value_name("POSITION")
.ignore_case(true)
.help("Where to place the legend for the memory widget.")
.value_parser([
"none",
"top-left",
"top",
"top-right",
"left",
"right",
"bottom-left",
"bottom",
"bottom-right",
]);
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
let enable_cache_memory = Arg::new("enable_cache_memory") let enable_cache_memory = Arg::new("enable_cache_memory")
@ -443,11 +461,11 @@ fn mem_args(cmd: Command) -> Command {
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.help("Enable collecting and displaying cache and buffer memory."); .help("Enable collecting and displaying cache and buffer memory.");
cmd.args(args![mem_as_value, enable_cache_memory]) cmd.args(args![mem_as_value, memory_legend, enable_cache_memory])
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
cmd.arg(mem_as_value) cmd.args(args![mem_as_value, memory_legend])
} }
} }
@ -464,6 +482,24 @@ fn network_args(cmd: Command) -> Command {
display is not tested anymore and may be broken.", display is not tested anymore and may be broken.",
); );
let network_legend = Arg::new("network_legend")
.long("network_legend")
.action(ArgAction::Set)
.value_name("POSITION")
.ignore_case(true)
.help("Where to place the legend for the network widget.")
.value_parser([
"none",
"top-left",
"top",
"top-right",
"left",
"right",
"bottom-left",
"bottom",
"bottom-right",
]);
let network_use_bytes = Arg::new("network_use_bytes") let network_use_bytes = Arg::new("network_use_bytes")
.long("network_use_bytes") .long("network_use_bytes")
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
@ -487,6 +523,7 @@ fn network_args(cmd: Command) -> Command {
cmd.args(args![ cmd.args(args![
use_old_network_legend, use_old_network_legend,
network_legend,
network_use_bytes, network_use_bytes,
network_use_log, network_use_log,
network_use_binary_prefix, network_use_binary_prefix,

View file

@ -68,6 +68,8 @@ pub(crate) struct ConfigFlags {
pub(crate) battery: Option<bool>, pub(crate) battery: Option<bool>,
pub(crate) disable_click: Option<bool>, pub(crate) disable_click: Option<bool>,
pub(crate) no_write: Option<bool>, pub(crate) no_write: Option<bool>,
pub(crate) network_legend: Option<String>,
pub(crate) memory_legend: Option<String>,
/// For built-in colour palettes. /// For built-in colour palettes.
pub(crate) color: Option<String>, pub(crate) color: Option<String>,
pub(crate) mem_as_value: Option<bool>, pub(crate) mem_as_value: Option<bool>,