From e43456207be0757fbb9c05f93a169063faf673b7 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Sun, 15 Nov 2020 05:16:47 -0500 Subject: feature: default colour schemes (#296) Adds some default colour choices to choose from. --- .vscode/settings.json | 1 + CHANGELOG.md | 8 ++ Cargo.lock | 78 ++------------ Cargo.toml | 2 +- README.md | 51 +++++---- src/bin/main.rs | 1 + src/canvas.rs | 153 ++++++++++---------------- src/canvas/canvas_colours.rs | 174 +++++++++++++++++++++++++----- src/canvas/canvas_colours/colour_utils.rs | 1 + src/canvas/widgets/battery_display.rs | 6 +- src/canvas/widgets/cpu_graph.rs | 7 +- src/canvas/widgets/disk_table.rs | 2 +- src/canvas/widgets/process_table.rs | 2 +- src/clap.rs | 31 ++++++ src/constants.rs | 121 +++++++++++++++++++++ src/options.rs | 47 ++++++-- tests/invalid_config_tests.rs | 13 --- tests/invalid_configs/empty_battery.toml | 2 - 18 files changed, 456 insertions(+), 244 deletions(-) delete mode 100644 tests/invalid_configs/empty_battery.toml diff --git a/.vscode/settings.json b/.vscode/settings.json index f06c40e6..f5f60e4f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -70,6 +70,7 @@ "n'th", "nixos", "noheader", + "nord", "ntdef", "nuget", "nvme", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6976b15f..eedb7ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,10 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#269](https://github.com/ClementTsang/bottom/pull/269): Add simple indicator for when data updating is frozen. +- [#296](https://github.com/ClementTsang/bottom/pull/296): Built-in colour themes. + ### Changes - [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation. +- [#296](https://github.com/ClementTsang/bottom/pull/296): Changed how we do battery theming. We now only set high, medium, and low colours and we deal with the ratios. + ### Bug Fixes - [#211](https://github.com/ClementTsang/bottom/pull/211): Fixes a bug where you could move down in the process widget even if the process widget search was closed. @@ -52,6 +56,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#291](https://github.com/ClementTsang/bottom/pull/291): Fixed spacing problems in basic CPU mode. +- [#296](https://github.com/ClementTsang/bottom/pull/296): Fixed an incorrect offset affecting the graph CPU colour mismatching the legend. + +- [#296](https://github.com/ClementTsang/bottom/pull/296): Removes an accidental extra comma in one of the headers in the disk widget. + ## [0.4.7] - 2020-08-26 ### Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index 688e4615..ea0717a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,7 @@ dependencies = [ "cargo-husky", "chrono", "clap", - "crossterm 0.18.2", + "crossterm", "ctrlc", "dirs-next", "fern", @@ -199,15 +199,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "cloudabi" version = "0.1.0" @@ -286,22 +277,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "crossterm" -version = "0.17.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f4919d60f26ae233e14233cc39746c8c8bb8cd7b05840ace83604917b51b6c7" -dependencies = [ - "bitflags", - "crossterm_winapi", - "lazy_static", - "libc", - "mio", - "parking_lot 0.10.2", - "signal-hook", - "winapi", -] - [[package]] name = "crossterm" version = "0.18.2" @@ -313,7 +288,7 @@ dependencies = [ "lazy_static", "libc", "mio", - "parking_lot 0.11.0", + "parking_lot", "signal-hook", "winapi", ] @@ -767,15 +742,6 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" -[[package]] -name = "lock_api" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] - [[package]] name = "lock_api" version = "0.4.1" @@ -957,9 +923,9 @@ checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" [[package]] name = "once_cell" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "ordered-float" @@ -970,16 +936,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "parking_lot" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.7.2", -] - [[package]] name = "parking_lot" version = "0.11.0" @@ -987,22 +943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" dependencies = [ "instant", - "lock_api 0.4.1", - "parking_lot_core 0.8.0", -] - -[[package]] -name = "parking_lot_core" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi 0.0.3", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "lock_api", + "parking_lot_core", ] [[package]] @@ -1012,7 +954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" dependencies = [ "cfg-if 0.1.10", - "cloudabi 0.1.0", + "cloudabi", "instant", "libc", "redox_syscall", @@ -1388,13 +1330,13 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "tui" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2eaeee894a1e9b90f80aa466fe59154fdb471980b5e104d8836fcea309ae17e" +checksum = "5d4e6c82bb967df89f20b875fa8835fab5d5622c6a5efa574a1f0b6d0aa6e8f6" dependencies = [ "bitflags", "cassowary", - "crossterm 0.17.7", + "crossterm", "unicode-segmentation", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index 7751354f..dbbcd86f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ serde = {version = "1.0", features = ["derive"] } sysinfo = "0.15.3" thiserror = "1.0.21" toml = "0.5.7" -tui = {version = "0.12.0", features = ["crossterm"], default-features = false } +tui = {version = "0.13.0", features = ["crossterm"], default-features = false } typed-builder = "0.7.0" unicode-segmentation = "1.6.0" unicode-width = "0.1" diff --git a/README.md b/README.md index 8594f7fe..afe7df74 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 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, expanding, and process killing.](assets/demo.gif) _Theme based on [gruvbox](https://github.com/morhetz/gruvbox) (see [sample config](./sample_configs/demo_config.toml))._ Recorded on version 0.4.7. +![Quick demo recording showing off searching, expanding, and process killing.](assets/demo.gif) _Theme based on [gruvbox](https://github.com/morhetz/gruvbox) (see [sample config](./sample_configs/demo_config.toml)). Font is [IBM Plex Mono](https://www.ibm.com/plex/), terminal is [Kitty](https://sw.kovidgoyal.net/kitty/)_ Recorded on version **0.4.7**. **Note**: If you are reading this on the master branch, then it may refer to in-development or un-released features/changes. 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. @@ -215,7 +215,9 @@ Run using `btm`. --battery Shows the battery widget. -S, --case_sensitive Enables case sensitivity by default. -c, --celsius Sets the temperature type to Celsius. + --color Use a color scheme, use --help for supported values. -C, --config Sets the location of the config file. + -u, --current_usage Sets process CPU% to be based on current CPU%. --debug Enables debug logging. -t, --default_time_value Default time value for graphs in ms. --default_widget_count Sets the n'th selected widget type as the default. @@ -224,19 +226,19 @@ Run using `btm`. -m, --dot_marker Uses a dot marker for graphs. -f, --fahrenheit Sets the temperature type to Fahrenheit. -g, --group Groups processes with the same name by default. + -h, --help Prints help information. Use --help for more info. -a, --hide_avg_cpu Hides the average CPU usage. --hide_table_gap Hides the spacing between table headers and entries. --hide_time Completely hides the time scaling. -k, --kelvin Sets the temperature type to Kelvin. -l, --left_legend Puts the CPU chart legend to the left side. + --no_write Disables writing to the config file. -r, --rate Sets a refresh rate in ms. -R, --regex Enables regex by default. -d, --time_delta The amount in ms changed upon zooming. - -u, --current_usage Sets process CPU% to be based on current CPU%. --use_old_network_legend DEPRECATED - uses the older network legend. - -W, --whole_word Enables whole-word matching by default. - -h, --help Prints help information. Use --help for more info. -V, --version Prints version information. + -W, --whole_word Enables whole-word matching by default. ``` ### Keybindings @@ -538,6 +540,7 @@ These are the following supported flag config values, which correspond to the fl | `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"]) | #### Theming @@ -545,25 +548,27 @@ The config file can be used to set custom colours for parts of the application u Supported named colours are one of the following strings: `Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, White`. -| Labels | Details | Example | -| ------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- | -| Table header colours | Colour of table headers | `table_header_color="255, 255, 255"` | -| CPU colour per core | Colour of each core. Read in order. | `cpu_core_colors=["#ffffff", "white", "255, 255, 255"]` | -| Average CPU colour | The average CPU color | `avg_cpu_color="White"` | -| All CPUs colour | The colour for the "All" CPU label | `all_cpu_color="White"` | -| RAM | The colour RAM will use | `ram_color="#ffffff"` | -| SWAP | The colour SWAP will use | `swap_color="#ffffff"` | -| RX | The colour rx will use | `rx_color="#ffffff"` | -| TX | The colour tx will use | `tx_color="#ffffff"` | -| Widget title colour | The colour of the label each widget has | `widget_title_color="#ffffff"` | -| Border colour | The colour of the border of unselected widgets | `border_color="#ffffff"` | -| Selected border colour | The colour of the border of selected widgets | `highlighted_border_color="#ffffff"` | -| Text colour | The colour of most text | `text_color="#ffffff"` | -| Graph colour | The colour of the lines and text of the graph | `graph_color="#ffffff"` | -| Cursor colour | The cursor's colour | `cursor_color="#ffffff"` | -| Selected text colour | The colour of text that is selected | `scroll_entry_text_color="#ffffff"` | -| Selected text background colour | The background colour of text that is selected | `scroll_entry_bg_color="#ffffff"` | -| Battery bar colours | Colour used is based on percentage and no. of colours | `battery_colors=["green", "yellow", "red"]` | +| Labels | Details | Example | +| ------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | +| Table header colours | Colour of table headers | `table_header_color="255, 255, 255"` | +| CPU colour per core | Colour of each core. Read in order. | `cpu_core_colors=["#ffffff", "white", "255, 255, 255"]` | +| Average CPU colour | The average CPU color | `avg_cpu_color="White"` | +| All CPUs colour | The colour for the "All" CPU label | `all_cpu_color="White"` | +| RAM | The colour RAM will use | `ram_color="#ffffff"` | +| SWAP | The colour SWAP will use | `swap_color="#ffffff"` | +| RX | The colour rx will use | `rx_color="#ffffff"` | +| TX | The colour tx will use | `tx_color="#ffffff"` | +| Widget title colour | The colour of the label each widget has | `widget_title_color="#ffffff"` | +| Border colour | The colour of the border of unselected widgets | `border_color="#ffffff"` | +| Selected border colour | The colour of the border of selected widgets | `highlighted_border_color="#ffffff"` | +| Text colour | The colour of most text | `text_color="#ffffff"` | +| Graph colour | The colour of the lines and text of the graph | `graph_color="#ffffff"` | +| Cursor colour | The cursor's colour | `cursor_color="#ffffff"` | +| Selected text colour | The colour of text that is selected | `scroll_entry_text_color="#ffffff"` | +| Selected text background colour | The background colour of text that is selected | `scroll_entry_bg_color="#ffffff"` | +| High battery level colour | The colour used for a high battery level (100% to 50%) | `high_battery_color="green"` | +| Medium battery level colour | The colour used for a medium battery level (50% to 10%) | `medium_battery_color="yellow"` | +| Low battery level colour | The colour used for a low battery level (10% to 0%) | `low_battery_color="red"` | #### Layout diff --git a/src/bin/main.rs b/src/bin/main.rs index 8db05b22..a059f4f0 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -70,6 +70,7 @@ fn main() -> Result<()> { app.app_config_fields.table_gap, app.app_config_fields.use_basic_mode, &config, + get_color_scheme(&matches, &config)?, )?; // Create termination mutex and cvar diff --git a/src/canvas.rs b/src/canvas.rs index d85f2444..626f4f7f 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -1,6 +1,5 @@ -use anyhow::Context; use itertools::izip; -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use tui::{ backend::Backend, @@ -25,6 +24,7 @@ use crate::{ data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData}, options::Config, utils::error, + utils::error::BottomError, }; mod canvas_colours; @@ -59,6 +59,35 @@ pub struct DisplayableData { pub battery_data: Vec, } +#[derive(Debug)] +pub enum ColourScheme { + Default, + DefaultLight, + Gruvbox, + GruvboxLight, + // Nord, + Custom, +} + +impl FromStr for ColourScheme { + type Err = BottomError; + + fn from_str(s: &str) -> error::Result { + let lower_case = s.to_lowercase(); + match lower_case.as_str() { + "default" => Ok(ColourScheme::Default), + "default-light" => Ok(ColourScheme::DefaultLight), + "gruvbox" => Ok(ColourScheme::Gruvbox), + "gruvbox-light" => Ok(ColourScheme::GruvboxLight), + // "nord" => Ok(ColourScheme::Nord), + _ => Err(BottomError::ConfigError(format!( + "\"{}\" is an invalid built-in color scheme.", + s + ))), + } + } +} + /// Handles the canvas' state. TODO: [OPT] implement this. pub struct Painter { pub colours: CanvasColours, @@ -78,6 +107,7 @@ pub struct Painter { impl Painter { pub fn init( widget_layout: BottomLayout, table_gap: u16, is_basic_mode: bool, config: &Config, + colour_scheme: ColourScheme, ) -> anyhow::Result { // Now for modularity; we have to also initialize the base layouts! // We want to do this ONCE and reuse; after this we can just construct @@ -161,117 +191,48 @@ impl Painter { table_height_offset: if is_basic_mode { 2 } else { 4 } + table_gap, }; - painter.generate_config_colours(config)?; + if let ColourScheme::Custom = colour_scheme { + painter.generate_config_colours(config)?; + } else { + painter.generate_colour_scheme(colour_scheme)?; + } painter.colours.generate_remaining_cpu_colours(); painter.complete_painter_init(); Ok(painter) } - pub fn generate_config_colours(&mut self, config: &Config) -> anyhow::Result<()> { + fn generate_config_colours(&mut self, config: &Config) -> anyhow::Result<()> { if let Some(colours) = &config.colors { - if let Some(border_color) = &colours.border_color { - self.colours - .set_border_colour(border_color) - .context("Update 'border_color' in your config file..")?; - } - - if let Some(highlighted_border_color) = &colours.highlighted_border_color { - self.colours - .set_highlighted_border_colour(highlighted_border_color) - .context("Update 'highlighted_border_color' in your config file..")?; - } - - if let Some(text_color) = &colours.text_color { - self.colours - .set_text_colour(text_color) - .context("Update 'text_color' in your config file..")?; - } - - if let Some(avg_cpu_color) = &colours.avg_cpu_color { - self.colours - .set_avg_cpu_colour(avg_cpu_color) - .context("Update 'avg_cpu_color' in your config file..")?; - } - - if let Some(all_cpu_color) = &colours.all_cpu_color { - self.colours - .set_all_cpu_colour(all_cpu_color) - .context("Update 'all_cpu_color' in your config file..")?; - } + self.colours.set_colours_from_palette(colours)?; + } - if let Some(cpu_core_colors) = &colours.cpu_core_colors { - self.colours - .set_cpu_colours(cpu_core_colors) - .context("Update 'cpu_core_colors' in your config file..")?; - } + Ok(()) + } - if let Some(ram_color) = &colours.ram_color { - self.colours - .set_ram_colour(ram_color) - .context("Update 'ram_color' in your config file..")?; + fn generate_colour_scheme(&mut self, colour_scheme: ColourScheme) -> anyhow::Result<()> { + match colour_scheme { + ColourScheme::Default => { + // Don't have to do anything. } - - if let Some(swap_color) = &colours.swap_color { + ColourScheme::DefaultLight => { self.colours - .set_swap_colour(swap_color) - .context("Update 'swap_color' in your config file..")?; + .set_colours_from_palette(&*DEFAULT_LIGHT_MODE_COLOUR_PALETTE)?; } - - if let Some(rx_color) = &colours.rx_color { + ColourScheme::Gruvbox => { self.colours - .set_rx_colour(rx_color) - .context("Update 'rx_color' in your config file..")?; + .set_colours_from_palette(&*GRUVBOX_COLOUR_PALETTE)?; } - - if let Some(tx_color) = &colours.tx_color { + ColourScheme::GruvboxLight => { self.colours - .set_tx_colour(tx_color) - .context("Update 'tx_color' in your config file..")?; + .set_colours_from_palette(&*GRUVBOX_LIGHT_COLOUR_PALETTE)?; } - - // if let Some(rx_total_color) = &colours.rx_total_color { - // painter.colours.set_rx_total_colour(rx_total_color)?; + // ColourScheme::Nord => { + // self.colours + // .set_colours_from_palette(&*NORD_COLOUR_PALETTE)?; // } - - // if let Some(tx_total_color) = &colours.tx_total_color { - // painter.colours.set_tx_total_colour(tx_total_color)?; - // } - - if let Some(table_header_color) = &colours.table_header_color { - self.colours - .set_table_header_colour(table_header_color) - .context("Update 'table_header_color' in your config file..")?; - } - - if let Some(scroll_entry_text_color) = &colours.selected_text_color { - self.colours - .set_scroll_entry_text_color(scroll_entry_text_color) - .context("Update 'selected_text_color' in your config file..")?; - } - - if let Some(scroll_entry_bg_color) = &colours.selected_bg_color { - self.colours - .set_scroll_entry_bg_color(scroll_entry_bg_color) - .context("Update 'selected_bg_color' in your config file..")?; - } - - if let Some(widget_title_color) = &colours.widget_title_color { - self.colours - .set_widget_title_colour(widget_title_color) - .context("Update 'widget_title_color' in your config file..")?; - } - - if let Some(graph_color) = &colours.graph_color { - self.colours - .set_graph_colour(graph_color) - .context("Update 'graph_color' in your config file..")?; - } - - if let Some(battery_colors) = &colours.battery_colors { - self.colours - .set_battery_colors(battery_colors) - .context("Update 'battery_colors' in your config file.")?; + ColourScheme::Custom => { + // This case should never occur, just do nothing. } } diff --git a/src/canvas/canvas_colours.rs b/src/canvas/canvas_colours.rs index 2876e24b..a42eb88c 100644 --- a/src/canvas/canvas_colours.rs +++ b/src/canvas/canvas_colours.rs @@ -1,9 +1,7 @@ -use tui::style::{Color, Style}; - +use crate::{constants::*, options::ConfigColours, utils::error}; +use anyhow::Context; use colour_utils::*; - -use crate::{constants::*, utils::error}; - +use tui::style::{Color, Style}; mod colour_utils; pub struct CanvasColours { @@ -60,6 +58,10 @@ impl Default for CanvasColours { Style::default().fg(Color::Red), Style::default().fg(Color::Yellow), Style::default().fg(Color::Yellow), + Style::default().fg(Color::Yellow), + Style::default().fg(Color::Green), + Style::default().fg(Color::Green), + Style::default().fg(Color::Green), Style::default().fg(Color::Green), Style::default().fg(Color::Green), Style::default().fg(Color::Green), @@ -71,6 +73,118 @@ impl Default for CanvasColours { } impl CanvasColours { + pub fn set_colours_from_palette(&mut self, colours: &ConfigColours) -> anyhow::Result<()> { + if let Some(border_color) = &colours.border_color { + self.set_border_colour(border_color) + .context("Update 'border_color' in your config file..")?; + } + + if let Some(highlighted_border_color) = &colours.highlighted_border_color { + self.set_highlighted_border_colour(highlighted_border_color) + .context("Update 'highlighted_border_color' in your config file..")?; + } + + if let Some(text_color) = &colours.text_color { + self.set_text_colour(text_color) + .context("Update 'text_color' in your config file..")?; + } + + if let Some(avg_cpu_color) = &colours.avg_cpu_color { + self.set_avg_cpu_colour(avg_cpu_color) + .context("Update 'avg_cpu_color' in your config file..")?; + } + + if let Some(all_cpu_color) = &colours.all_cpu_color { + self.set_all_cpu_colour(all_cpu_color) + .context("Update 'all_cpu_color' in your config file..")?; + } + + if let Some(cpu_core_colors) = &colours.cpu_core_colors { + self.set_cpu_colours(cpu_core_colors) + .context("Update 'cpu_core_colors' in your config file..")?; + } + + if let Some(ram_color) = &colours.ram_color { + self.set_ram_colour(ram_color) + .context("Update 'ram_color' in your config file..")?; + } + + if let Some(swap_color) = &colours.swap_color { + self.set_swap_colour(swap_color) + .context("Update 'swap_color' in your config file..")?; + } + + if let Some(rx_color) = &colours.rx_color { + self.set_rx_colour(rx_color) + .context("Update 'rx_color' in your config file..")?; + } + + if let Some(tx_color) = &colours.tx_color { + self.set_tx_colour(tx_color) + .context("Update 'tx_color' in your config file..")?; + } + + if let Some(table_header_color) = &colours.table_header_color { + self.set_table_header_colour(table_header_color) + .context("Update 'table_header_color' in your config file..")?; + } + + if let Some(scroll_entry_text_color) = &colours.selected_text_color { + self.set_scroll_entry_text_color(scroll_entry_text_color) + .context("Update 'selected_text_color' in your config file..")?; + } + + if let Some(scroll_entry_bg_color) = &colours.selected_bg_color { + self.set_scroll_entry_bg_color(scroll_entry_bg_color) + .context("Update 'selected_bg_color' in your config file..")?; + } + + if let Some(widget_title_color) = &colours.widget_title_color { + self.set_widget_title_colour(widget_title_color) + .context("Update 'widget_title_color' in your config file..")?; + } + + if let Some(graph_color) = &colours.graph_color { + self.set_graph_colour(graph_color) + .context("Update 'graph_color' in your config file..")?; + } + + if let Some(high_battery_color) = &colours.high_battery_color { + self.set_high_battery_color(high_battery_color) + .context("Update 'high_battery_color' in your config file.")?; + } + + if let Some(medium_battery_color) = &colours.medium_battery_color { + self.set_medium_battery_color(medium_battery_color) + .context("Update 'medium_battery_color' in your config file.")?; + } + + if let Some(low_battery_color) = &colours.low_battery_color { + self.set_low_battery_color(low_battery_color) + .context("Update 'low_battery_color' in your config file.")?; + } + + if let Some(disabled_text_color) = &colours.disabled_text_color { + self.set_disabled_text_colour(disabled_text_color) + .context("Update 'disabled_text_color' in your config file.")?; + } + + if let Some(rx_total_color) = &colours.rx_total_color { + self.set_rx_total_colour(rx_total_color)?; + } + + if let Some(tx_total_color) = &colours.tx_total_color { + self.set_tx_total_colour(tx_total_color)?; + } + + Ok(()) + } + + pub fn set_disabled_text_colour(&mut self, colour: &str) -> error::Result<()> { + self.disabled_text_style = get_style_from_config(colour)?; + Ok(()) + } + pub fn set_text_colour(&mut self, colour: &str) -> error::Result<()> { self.text_style = get_style_from_config(colour)?; Ok(()) @@ -113,15 +227,15 @@ impl CanvasColours { Ok(()) } - // pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> { - // self.total_rx_style = get_style_from_config(colour)?; - // Ok(()) - // } + pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> { + self.total_rx_style = get_style_from_config(colour)?; + Ok(()) + } - // pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> { - // self.total_tx_style = get_style_from_config(colour)?; - // Ok(()) - // } + pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> { + self.total_tx_style = get_style_from_config(colour)?; + Ok(()) + } pub fn set_avg_cpu_colour(&mut self, colour: &str) -> error::Result<()> { self.avg_colour_style = get_style_from_config(colour)?; @@ -176,19 +290,27 @@ impl CanvasColours { Ok(()) } - pub fn set_battery_colors(&mut self, colours: &[String]) -> error::Result<()> { - if colours.is_empty() { - Err(error::BottomError::ConfigError( - "battery colour list must have at least one colour.".to_string(), - )) - } else { - let generated_colours: Result, _> = colours - .iter() - .map(|colour| get_style_from_config(colour)) - .collect(); + pub fn set_high_battery_color(&mut self, colour: &str) -> error::Result<()> { + let style = get_style_from_config(colour)?; + self.battery_bar_styles[0] = style; + self.battery_bar_styles[1] = style; + self.battery_bar_styles[2] = style; + self.battery_bar_styles[3] = style; + self.battery_bar_styles[4] = style; + self.battery_bar_styles[5] = style; + Ok(()) + } - self.battery_bar_styles = generated_colours?; - Ok(()) - } + pub fn set_medium_battery_color(&mut self, colour: &str) -> error::Result<()> { + let style = get_style_from_config(colour)?; + self.battery_bar_styles[6] = style; + self.battery_bar_styles[7] = style; + self.battery_bar_styles[8] = style; + Ok(()) + } + + pub fn set_low_battery_color(&mut self, colour: &str) -> error::Result<()> { + self.battery_bar_styles[9] = get_style_from_config(colour)?; + Ok(()) } } diff --git a/src/canvas/canvas_colours/colour_utils.rs b/src/canvas/canvas_colours/colour_utils.rs index f94b9bd9..d104d483 100644 --- a/src/canvas/canvas_colours/colour_utils.rs +++ b/src/canvas/canvas_colours/colour_utils.rs @@ -26,6 +26,7 @@ lazy_static! { ("magenta", Color::Magenta), ("cyan", Color::Cyan), ("gray", Color::Gray), + ("grey", Color::Gray), ("darkgray", Color::DarkGray), ("lightred", Color::LightRed), ("lightgreen", Color::LightGreen), diff --git a/src/canvas/widgets/battery_display.rs b/src/canvas/widgets/battery_display.rs index e27c1aef..ae911d42 100644 --- a/src/canvas/widgets/battery_display.rs +++ b/src/canvas/widgets/battery_display.rs @@ -143,10 +143,10 @@ impl BatteryDisplayWidget for Painter { item.iter(), if itx == 0 { let colour_index = ((charge_percentage - * self.colours.battery_bar_styles.len() as f64 - - 1.0) + * self.colours.battery_bar_styles.len() as f64) / 100.0) - .floor() as usize; + .ceil() as usize + - 1; *self .colours .battery_bar_styles diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index bce8b699..e03c9032 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -220,7 +220,12 @@ impl CpuGraphWidget for Painter { self.colours.cpu_colour_styles[cpu_widget_state .scroll_state .current_scroll_position - % self.colours.cpu_colour_styles.len()] + - 1 // Because of the all position + - (if show_avg_cpu { + AVG_POSITION + } else { + 0 + }) % self.colours.cpu_colour_styles.len()] }) .data(&cpu.cpu_data[..]) .graph_type(tui::widgets::GraphType::Line)] diff --git a/src/canvas/widgets/disk_table.rs b/src/canvas/widgets/disk_table.rs index c52062b5..9d249f4e 100644 --- a/src/canvas/widgets/disk_table.rs +++ b/src/canvas/widgets/disk_table.rs @@ -170,7 +170,7 @@ impl DiskTableWidget for Painter { Span::styled(" Disk ", self.colours.widget_title_style), Span::styled( format!( - "─{}─ Esc to go back, ", + "─{}─ Esc to go back ", "─".repeat(usize::from(draw_loc.width).saturating_sub( UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2 )) diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 727abb94..0ee25b1f 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -408,7 +408,7 @@ impl ProcessTableWidget for Painter { } }); - // TODO: gotop's "x out of y" thing is really nice to help keep track of the scroll position. Add to everything? + // 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) diff --git a/src/clap.rs b/src/clap.rs index 2a03e9b4..5f6d7667 100644 --- a/src/clap.rs +++ b/src/clap.rs @@ -197,6 +197,36 @@ entire query by default.\n\n", Sets the location of the config file. Expects a config file in the TOML format. If it doesn't exist, one is created.\n\n\n", ); + let color = Arg::with_name("color") + .long("color") + .takes_value(true) + .value_name("COLOR SCHEME") + .help("Use a color scheme, use --help for supported values.") + .long_help( + "\ +Use a pre-defined color scheme. Currently supported values are: + ++------------------------------------------------------------+ +| default | ++------------------------------------------------------------+ +| default-light (default but for use with light backgrounds) | ++------------------------------------------------------------+ +| gruvbox (a bright theme with 'retro groove' colors) | ++------------------------------------------------------------+ +| gruvbox-light (gruvbox but for use with light backgrounds) | ++------------------------------------------------------------+ + +Defaults to \"default\". +\n\n", + ) + .possible_values(&[ + "default", + "default-light", + "gruvbox", + "gruvbox-light", + "nord", + ]) + .hide_possible_values(true); let default_time_value = Arg::with_name("default_time_value") .short("t") .long("default_time_value") @@ -313,6 +343,7 @@ The minimum is 1s (1000), and defaults to 15s (15000).\n\n\n", .arg(battery) .arg(case_sensitive) .arg(config_location) + .arg(color) .arg(debug) .arg(default_time_value) .arg(default_widget_count) diff --git a/src/constants.rs b/src/constants.rs index 8bc6f11d..91e9419c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,7 @@ use lazy_static::lazy_static; +use crate::options::ConfigColours; + // Default widget ID pub const DEFAULT_WIDGET_ID: u64 = 56709; @@ -37,6 +39,125 @@ lazy_static! { tui::style::Style::default().fg(tui::style::Color::LightBlue); } +// Colour profiles +lazy_static! { + pub static ref DEFAULT_LIGHT_MODE_COLOUR_PALETTE: ConfigColours = ConfigColours { + text_color: Some("black".to_string()), + border_color: Some("black".to_string()), + table_header_color: Some("black".to_string()), + widget_title_color: Some("black".to_string()), + selected_text_color: Some("white".to_string()), + graph_color: Some("black".to_string()), + disabled_text_color: Some("gray".to_string()), + ..ConfigColours::default() + }; + pub static ref GRUVBOX_COLOUR_PALETTE: ConfigColours = ConfigColours { + table_header_color: Some("#ebdbb2".to_string()), + all_cpu_color: Some("#cc241d".to_string()), + avg_cpu_color: Some("#98971a".to_string()), + cpu_core_colors: Some(vec![ + "#d79921".to_string(), + "#458588".to_string(), + "#b16286".to_string(), + "#689d6a".to_string(), + "#fb4934".to_string(), + "#b8bb26".to_string(), + "#fe8019".to_string(), + "#fabd2f".to_string(), + "#83a598".to_string(), + "#d3869b".to_string(), + "#8ec07c".to_string(), + "#d65d0e".to_string(), + "#fbf1c7".to_string(), + "#ebdbb2".to_string(), + "#d5c4a1".to_string(), + "#bdae93".to_string(), + "#a89984".to_string(), + ]), + ram_color: Some("#458588".to_string()), + swap_color: Some("#fabd2f".to_string()), + rx_color: Some("#458588".to_string()), + tx_color: Some("#fabd2f".to_string()), + rx_total_color: Some("#83a598".to_string()), + tx_total_color: Some("#d79921".to_string()), + border_color: Some("#ebdbb2".to_string()), + highlighted_border_color: Some("#fe8019".to_string()), + disabled_text_color: Some("#665c54".to_string()), + text_color: Some("#ebdbb2".to_string()), + selected_text_color: Some("#1d2021".to_string()), + selected_bg_color: Some("#ebdbb2".to_string()), + widget_title_color: Some("#ebdbb2".to_string()), + graph_color: Some("#ebdbb2".to_string()), + high_battery_color: Some("#98971a".to_string()), + medium_battery_color: Some("#fabd2f".to_string()), + low_battery_color: Some("#fb4934".to_string()) + }; + pub static ref GRUVBOX_LIGHT_COLOUR_PALETTE: ConfigColours = ConfigColours { + table_header_color: Some("#3c3836".to_string()), + all_cpu_color: Some("#cc241d".to_string()), + avg_cpu_color: Some("#98971a".to_string()), + cpu_core_colors: Some(vec![ + "#d79921".to_string(), + "#458588".to_string(), + "#b16286".to_string(), + "#689d6a".to_string(), + "#fb4934".to_string(), + "#b8bb26".to_string(), + "#fe8019".to_string(), + "#fabd2f".to_string(), + "#83a598".to_string(), + "#d3869b".to_string(), + "#8ec07c".to_string(), + "#d65d0e".to_string(), + "#928374".to_string(), + "#665c54".to_string(), + "#504945".to_string(), + "#3c3836".to_string(), + "#282828".to_string(), + ]), + ram_color: Some("#458588".to_string()), + swap_color: Some("#cc241d".to_string()), + rx_color: Some("#458588".to_string()), + tx_color: Some("#cc241d".to_string()), + rx_total_color: Some("#83a598".to_string()), + tx_total_color: Some("#9d0006".to_string()), + border_color: Some("#3c3836".to_string()), + highlighted_border_color: Some("#fe8019".to_string()), + disabled_text_color: Some("#665c54".to_string()), + text_color: Some("#3c3836".to_string()), + selected_text_color: Some("#f9f5d7".to_string()), + selected_bg_color: Some("#665c54".to_string()), + widget_title_color: Some("#3c3836".to_string()), + graph_color: Some("#3c3836".to_string()), + high_battery_color: Some("#98971a".to_string()), + medium_battery_color: Some("#fabd2f".to_string()), + low_battery_color: Some("#fb4934".to_string()) + }; + // pub static ref NORD_COLOUR_PALETTE: ConfigColours = ConfigColours { + // table_header_color: None, + // all_cpu_color: None, + // avg_cpu_color: None, + // cpu_core_colors: None, + // ram_color: None, + // swap_color: None, + // rx_color: None, + // tx_color: None, + // rx_total_color: None, + // tx_total_color: None, + // border_color: None, + // highlighted_border_color: None, + // text_color: None, + // selected_text_color: None, + // selected_bg_color: None, + // widget_title_color: None, + // graph_color: None, + // high_battery_color: None, + // medium_battery_color: None, + // low_battery_color: None, + // disabled_text_color: None, + // }; +} + // FIXME: [HELP] I wanna update this before release... it's missing mouse too. // Help text pub const HELP_CONTENTS_TEXT: [&str; 8] = [ diff --git a/src/options.rs b/src/options.rs index 27d722ae..07be698f 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,13 +1,16 @@ use regex::Regex; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, time::Instant}; use std::{ + borrow::Cow, collections::{HashMap, HashSet}, path::PathBuf, + str::FromStr, + time::Instant, }; use crate::{ app::{layout_manager::*, *}, + canvas::ColourScheme, constants::*, utils::error::{self, BottomError}, }; @@ -112,6 +115,9 @@ pub struct ConfigFlags { #[builder(default, setter(strip_option))] pub no_write: Option, + #[builder(default, setter(strip_option))] + pub color: Option, + // This is a huge hack to enable hashmap functionality WITHOUT being able to serializing the field. // Basically, keep a hashmap in the struct, and convert to a vector every time. #[builder(default, setter(strip_option))] @@ -164,16 +170,19 @@ pub struct ConfigColours { pub swap_color: Option, pub rx_color: Option, pub tx_color: Option, - pub rx_total_color: Option, - pub tx_total_color: Option, + pub rx_total_color: Option, // These only affect basic mode. + pub tx_total_color: Option, // These only affect basic mode. pub border_color: Option, pub highlighted_border_color: Option, + pub disabled_text_color: Option, pub text_color: Option, pub selected_text_color: Option, pub selected_bg_color: Option, pub widget_title_color: Option, pub graph_color: Option, - pub battery_colors: Option>, + pub high_battery_color: Option, + pub medium_battery_color: Option, + pub low_battery_color: Option, } #[derive(Clone, Debug, Default, Deserialize, Serialize)] @@ -803,7 +812,7 @@ fn get_disable_click(matches: &clap::ArgMatches<'static>, config: &Config) -> bo false } -pub fn get_use_old_network_legend(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_use_old_network_legend(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("use_old_network_legend") { return true; } else if let Some(flags) = &config.flags { @@ -814,7 +823,7 @@ pub fn get_use_old_network_legend(matches: &clap::ArgMatches<'static>, config: & false } -pub fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("hide_table_gap") { return true; } else if let Some(flags) = &config.flags { @@ -825,7 +834,7 @@ pub fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config) false } -pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("battery") { return true; } else if let Some(flags) = &config.flags { @@ -836,7 +845,7 @@ pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> false } -pub fn get_no_write(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_no_write(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("no_write") { return true; } else if let Some(flags) = &config.flags { @@ -847,7 +856,7 @@ pub fn get_no_write(matches: &clap::ArgMatches<'static>, config: &Config) -> boo false } -pub fn get_ignore_list(ignore_list: &Option) -> error::Result> { +fn get_ignore_list(ignore_list: &Option) -> error::Result> { if let Some(ignore_list) = ignore_list { let list: Result, _> = ignore_list .list @@ -888,3 +897,23 @@ pub fn get_ignore_list(ignore_list: &Option) -> error::Result