feature: default colour schemes (#296)

Adds some default colour choices to choose from.
This commit is contained in:
Clement Tsang 2020-11-15 05:16:47 -05:00 committed by GitHub
parent 4573194cec
commit e43456207b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 456 additions and 244 deletions

View file

@ -70,6 +70,7 @@
"n'th", "n'th",
"nixos", "nixos",
"noheader", "noheader",
"nord",
"ntdef", "ntdef",
"nuget", "nuget",
"nvme", "nvme",

View file

@ -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. - [#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 ### Changes
- [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation. - [#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 ### 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. - [#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. - [#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 ## [0.4.7] - 2020-08-26
### Bug Fixes ### Bug Fixes

78
Cargo.lock generated
View file

@ -117,7 +117,7 @@ dependencies = [
"cargo-husky", "cargo-husky",
"chrono", "chrono",
"clap", "clap",
"crossterm 0.18.2", "crossterm",
"ctrlc", "ctrlc",
"dirs-next", "dirs-next",
"fern", "fern",
@ -199,15 +199,6 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.1.0" version = "0.1.0"
@ -286,22 +277,6 @@ dependencies = [
"lazy_static", "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]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.18.2" version = "0.18.2"
@ -313,7 +288,7 @@ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
"mio", "mio",
"parking_lot 0.11.0", "parking_lot",
"signal-hook", "signal-hook",
"winapi", "winapi",
] ]
@ -767,15 +742,6 @@ version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 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]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.1" version = "0.4.1"
@ -957,9 +923,9 @@ checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.4.1" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
@ -970,16 +936,6 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.0" version = "0.11.0"
@ -987,22 +943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
dependencies = [ dependencies = [
"instant", "instant",
"lock_api 0.4.1", "lock_api",
"parking_lot_core 0.8.0", "parking_lot_core",
]
[[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",
] ]
[[package]] [[package]]
@ -1012,7 +954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
"cloudabi 0.1.0", "cloudabi",
"instant", "instant",
"libc", "libc",
"redox_syscall", "redox_syscall",
@ -1388,13 +1330,13 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]] [[package]]
name = "tui" name = "tui"
version = "0.12.0" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2eaeee894a1e9b90f80aa466fe59154fdb471980b5e104d8836fcea309ae17e" checksum = "5d4e6c82bb967df89f20b875fa8835fab5d5622c6a5efa574a1f0b6d0aa6e8f6"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",
"crossterm 0.17.7", "crossterm",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
] ]

View file

@ -42,7 +42,7 @@ serde = {version = "1.0", features = ["derive"] }
sysinfo = "0.15.3" sysinfo = "0.15.3"
thiserror = "1.0.21" thiserror = "1.0.21"
toml = "0.5.7" 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" typed-builder = "0.7.0"
unicode-segmentation = "1.6.0" unicode-segmentation = "1.6.0"
unicode-width = "0.1" unicode-width = "0.1"

View file

@ -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). 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. **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. --battery Shows the battery widget.
-S, --case_sensitive Enables case sensitivity by default. -S, --case_sensitive Enables case sensitivity by default.
-c, --celsius Sets the temperature type to Celsius. -c, --celsius Sets the temperature type to Celsius.
--color <COLOR SCHEME> Use a color scheme, use --help for supported values.
-C, --config <CONFIG PATH> Sets the location of the config file. -C, --config <CONFIG PATH> Sets the location of the config file.
-u, --current_usage Sets process CPU% to be based on current CPU%.
--debug Enables debug logging. --debug Enables debug logging.
-t, --default_time_value <MS> Default time value for graphs in ms. -t, --default_time_value <MS> Default time value for graphs in ms.
--default_widget_count <INT> Sets the n'th selected widget type as the default. --default_widget_count <INT> 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. -m, --dot_marker Uses a dot marker for graphs.
-f, --fahrenheit Sets the temperature type to Fahrenheit. -f, --fahrenheit Sets the temperature type to Fahrenheit.
-g, --group Groups processes with the same name by default. -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. -a, --hide_avg_cpu Hides the average CPU usage.
--hide_table_gap Hides the spacing between table headers and entries. --hide_table_gap Hides the spacing between table headers and entries.
--hide_time Completely hides the time scaling. --hide_time Completely hides the time scaling.
-k, --kelvin Sets the temperature type to Kelvin. -k, --kelvin Sets the temperature type to Kelvin.
-l, --left_legend Puts the CPU chart legend to the left side. -l, --left_legend Puts the CPU chart legend to the left side.
--no_write Disables writing to the config file.
-r, --rate <MS> Sets a refresh rate in ms. -r, --rate <MS> Sets a refresh rate in ms.
-R, --regex Enables regex by default. -R, --regex Enables regex by default.
-d, --time_delta <MS> The amount in ms changed upon zooming. -d, --time_delta <MS> 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. --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. -V, --version Prints version information.
-W, --whole_word Enables whole-word matching by default.
``` ```
### Keybindings ### 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_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) |
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | | `default_widget_count` | Unsigned Int (represents which `default_widget_type`) |
| `disable_click` | Boolean | | `disable_click` | Boolean |
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light"]) |
#### Theming #### 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`. 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 | | Labels | Details | Example |
| ------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- | | ------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- |
| Table header colours | Colour of table headers | `table_header_color="255, 255, 255"` | | 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"]` | | 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"` | | 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"` | | All CPUs colour | The colour for the "All" CPU label | `all_cpu_color="White"` |
| RAM | The colour RAM will use | `ram_color="#ffffff"` | | RAM | The colour RAM will use | `ram_color="#ffffff"` |
| SWAP | The colour SWAP will use | `swap_color="#ffffff"` | | SWAP | The colour SWAP will use | `swap_color="#ffffff"` |
| RX | The colour rx will use | `rx_color="#ffffff"` | | RX | The colour rx will use | `rx_color="#ffffff"` |
| TX | The colour tx will use | `tx_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"` | | 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"` | | 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"` | | 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"` | | 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"` | | Graph colour | The colour of the lines and text of the graph | `graph_color="#ffffff"` |
| Cursor colour | The cursor's colour | `cursor_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 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"` | | 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"]` | | 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 #### Layout

View file

@ -70,6 +70,7 @@ fn main() -> Result<()> {
app.app_config_fields.table_gap, app.app_config_fields.table_gap,
app.app_config_fields.use_basic_mode, app.app_config_fields.use_basic_mode,
&config, &config,
get_color_scheme(&matches, &config)?,
)?; )?;
// Create termination mutex and cvar // Create termination mutex and cvar

View file

@ -1,6 +1,5 @@
use anyhow::Context;
use itertools::izip; use itertools::izip;
use std::collections::HashMap; use std::{collections::HashMap, str::FromStr};
use tui::{ use tui::{
backend::Backend, backend::Backend,
@ -25,6 +24,7 @@ use crate::{
data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData}, data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData},
options::Config, options::Config,
utils::error, utils::error,
utils::error::BottomError,
}; };
mod canvas_colours; mod canvas_colours;
@ -59,6 +59,35 @@ pub struct DisplayableData {
pub battery_data: Vec<ConvertedBatteryData>, pub battery_data: Vec<ConvertedBatteryData>,
} }
#[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<Self> {
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. /// Handles the canvas' state. TODO: [OPT] implement this.
pub struct Painter { pub struct Painter {
pub colours: CanvasColours, pub colours: CanvasColours,
@ -78,6 +107,7 @@ pub struct Painter {
impl Painter { impl Painter {
pub fn init( pub fn init(
widget_layout: BottomLayout, table_gap: u16, is_basic_mode: bool, config: &Config, widget_layout: BottomLayout, table_gap: u16, is_basic_mode: bool, config: &Config,
colour_scheme: ColourScheme,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
// Now for modularity; we have to also initialize the base layouts! // 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 // 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, 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.colours.generate_remaining_cpu_colours();
painter.complete_painter_init(); painter.complete_painter_init();
Ok(painter) 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(colours) = &config.colors {
if let Some(border_color) = &colours.border_color { self.colours.set_colours_from_palette(colours)?;
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 { Ok(())
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 { fn generate_colour_scheme(&mut self, colour_scheme: ColourScheme) -> anyhow::Result<()> {
self.colours match colour_scheme {
.set_text_colour(text_color) ColourScheme::Default => {
.context("Update 'text_color' in your config file..")?; // Don't have to do anything.
} }
ColourScheme::DefaultLight => {
if let Some(avg_cpu_color) = &colours.avg_cpu_color {
self.colours self.colours
.set_avg_cpu_colour(avg_cpu_color) .set_colours_from_palette(&*DEFAULT_LIGHT_MODE_COLOUR_PALETTE)?;
.context("Update 'avg_cpu_color' in your config file..")?;
} }
ColourScheme::Gruvbox => {
if let Some(all_cpu_color) = &colours.all_cpu_color {
self.colours self.colours
.set_all_cpu_colour(all_cpu_color) .set_colours_from_palette(&*GRUVBOX_COLOUR_PALETTE)?;
.context("Update 'all_cpu_color' in your config file..")?;
} }
ColourScheme::GruvboxLight => {
if let Some(cpu_core_colors) = &colours.cpu_core_colors {
self.colours self.colours
.set_cpu_colours(cpu_core_colors) .set_colours_from_palette(&*GRUVBOX_LIGHT_COLOUR_PALETTE)?;
.context("Update 'cpu_core_colors' in your config file..")?;
} }
// ColourScheme::Nord => {
if let Some(ram_color) = &colours.ram_color { // self.colours
self.colours // .set_colours_from_palette(&*NORD_COLOUR_PALETTE)?;
.set_ram_colour(ram_color)
.context("Update 'ram_color' in your config file..")?;
}
if let Some(swap_color) = &colours.swap_color {
self.colours
.set_swap_colour(swap_color)
.context("Update 'swap_color' in your config file..")?;
}
if let Some(rx_color) = &colours.rx_color {
self.colours
.set_rx_colour(rx_color)
.context("Update 'rx_color' in your config file..")?;
}
if let Some(tx_color) = &colours.tx_color {
self.colours
.set_tx_colour(tx_color)
.context("Update 'tx_color' in your config file..")?;
}
// if let Some(rx_total_color) = &colours.rx_total_color {
// painter.colours.set_rx_total_colour(rx_total_color)?;
// } // }
ColourScheme::Custom => {
// if let Some(tx_total_color) = &colours.tx_total_color { // This case should never occur, just do nothing.
// 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.")?;
} }
} }

View file

@ -1,9 +1,7 @@
use tui::style::{Color, Style}; use crate::{constants::*, options::ConfigColours, utils::error};
use anyhow::Context;
use colour_utils::*; use colour_utils::*;
use tui::style::{Color, Style};
use crate::{constants::*, utils::error};
mod colour_utils; mod colour_utils;
pub struct CanvasColours { pub struct CanvasColours {
@ -60,6 +58,10 @@ impl Default for CanvasColours {
Style::default().fg(Color::Red), Style::default().fg(Color::Red),
Style::default().fg(Color::Yellow), Style::default().fg(Color::Yellow),
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), 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 { 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<()> { pub fn set_text_colour(&mut self, colour: &str) -> error::Result<()> {
self.text_style = get_style_from_config(colour)?; self.text_style = get_style_from_config(colour)?;
Ok(()) Ok(())
@ -113,15 +227,15 @@ impl CanvasColours {
Ok(()) Ok(())
} }
// pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> { pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> {
// self.total_rx_style = get_style_from_config(colour)?; self.total_rx_style = get_style_from_config(colour)?;
// Ok(()) Ok(())
// } }
// pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> { pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> {
// self.total_tx_style = get_style_from_config(colour)?; self.total_tx_style = get_style_from_config(colour)?;
// Ok(()) Ok(())
// } }
pub fn set_avg_cpu_colour(&mut self, colour: &str) -> error::Result<()> { pub fn set_avg_cpu_colour(&mut self, colour: &str) -> error::Result<()> {
self.avg_colour_style = get_style_from_config(colour)?; self.avg_colour_style = get_style_from_config(colour)?;
@ -176,19 +290,27 @@ impl CanvasColours {
Ok(()) Ok(())
} }
pub fn set_battery_colors(&mut self, colours: &[String]) -> error::Result<()> { pub fn set_high_battery_color(&mut self, colour: &str) -> error::Result<()> {
if colours.is_empty() { let style = get_style_from_config(colour)?;
Err(error::BottomError::ConfigError( self.battery_bar_styles[0] = style;
"battery colour list must have at least one colour.".to_string(), self.battery_bar_styles[1] = style;
)) self.battery_bar_styles[2] = style;
} else { self.battery_bar_styles[3] = style;
let generated_colours: Result<Vec<_>, _> = colours self.battery_bar_styles[4] = style;
.iter() self.battery_bar_styles[5] = style;
.map(|colour| get_style_from_config(colour)) Ok(())
.collect(); }
self.battery_bar_styles = generated_colours?; pub fn set_medium_battery_color(&mut self, colour: &str) -> error::Result<()> {
Ok(()) 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(())
} }
} }

View file

@ -26,6 +26,7 @@ lazy_static! {
("magenta", Color::Magenta), ("magenta", Color::Magenta),
("cyan", Color::Cyan), ("cyan", Color::Cyan),
("gray", Color::Gray), ("gray", Color::Gray),
("grey", Color::Gray),
("darkgray", Color::DarkGray), ("darkgray", Color::DarkGray),
("lightred", Color::LightRed), ("lightred", Color::LightRed),
("lightgreen", Color::LightGreen), ("lightgreen", Color::LightGreen),

View file

@ -143,10 +143,10 @@ impl BatteryDisplayWidget for Painter {
item.iter(), item.iter(),
if itx == 0 { if itx == 0 {
let colour_index = ((charge_percentage let colour_index = ((charge_percentage
* self.colours.battery_bar_styles.len() as f64 * self.colours.battery_bar_styles.len() as f64)
- 1.0)
/ 100.0) / 100.0)
.floor() as usize; .ceil() as usize
- 1;
*self *self
.colours .colours
.battery_bar_styles .battery_bar_styles

View file

@ -220,7 +220,12 @@ impl CpuGraphWidget for Painter {
self.colours.cpu_colour_styles[cpu_widget_state self.colours.cpu_colour_styles[cpu_widget_state
.scroll_state .scroll_state
.current_scroll_position .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[..]) .data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)] .graph_type(tui::widgets::GraphType::Line)]

View file

@ -170,7 +170,7 @@ impl DiskTableWidget for Painter {
Span::styled(" Disk ", self.colours.widget_title_style), Span::styled(" Disk ", self.colours.widget_title_style),
Span::styled( Span::styled(
format!( format!(
"─{}─ Esc to go back, ", "─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub( "".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2 UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
)) ))

View file

@ -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( f.render_stateful_widget(
Table::new(process_headers.iter(), process_rows) Table::new(process_headers.iter(), process_rows)
.block(process_block) .block(process_block)

View file

@ -197,6 +197,36 @@ entire query by default.\n\n",
Sets the location of the config file. Expects a config 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", 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") let default_time_value = Arg::with_name("default_time_value")
.short("t") .short("t")
.long("default_time_value") .long("default_time_value")
@ -313,6 +343,7 @@ The minimum is 1s (1000), and defaults to 15s (15000).\n\n\n",
.arg(battery) .arg(battery)
.arg(case_sensitive) .arg(case_sensitive)
.arg(config_location) .arg(config_location)
.arg(color)
.arg(debug) .arg(debug)
.arg(default_time_value) .arg(default_time_value)
.arg(default_widget_count) .arg(default_widget_count)

View file

@ -1,5 +1,7 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use crate::options::ConfigColours;
// Default widget ID // Default widget ID
pub const DEFAULT_WIDGET_ID: u64 = 56709; pub const DEFAULT_WIDGET_ID: u64 = 56709;
@ -37,6 +39,125 @@ lazy_static! {
tui::style::Style::default().fg(tui::style::Color::LightBlue); 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. // FIXME: [HELP] I wanna update this before release... it's missing mouse too.
// Help text // Help text
pub const HELP_CONTENTS_TEXT: [&str; 8] = [ pub const HELP_CONTENTS_TEXT: [&str; 8] = [

View file

@ -1,13 +1,16 @@
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{borrow::Cow, time::Instant};
use std::{ use std::{
borrow::Cow,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
path::PathBuf, path::PathBuf,
str::FromStr,
time::Instant,
}; };
use crate::{ use crate::{
app::{layout_manager::*, *}, app::{layout_manager::*, *},
canvas::ColourScheme,
constants::*, constants::*,
utils::error::{self, BottomError}, utils::error::{self, BottomError},
}; };
@ -112,6 +115,9 @@ pub struct ConfigFlags {
#[builder(default, setter(strip_option))] #[builder(default, setter(strip_option))]
pub no_write: Option<bool>, pub no_write: Option<bool>,
#[builder(default, setter(strip_option))]
pub color: Option<String>,
// This is a huge hack to enable hashmap functionality WITHOUT being able to serializing the field. // 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. // Basically, keep a hashmap in the struct, and convert to a vector every time.
#[builder(default, setter(strip_option))] #[builder(default, setter(strip_option))]
@ -164,16 +170,19 @@ pub struct ConfigColours {
pub swap_color: Option<String>, pub swap_color: Option<String>,
pub rx_color: Option<String>, pub rx_color: Option<String>,
pub tx_color: Option<String>, pub tx_color: Option<String>,
pub rx_total_color: Option<String>, pub rx_total_color: Option<String>, // These only affect basic mode.
pub tx_total_color: Option<String>, pub tx_total_color: Option<String>, // These only affect basic mode.
pub border_color: Option<String>, pub border_color: Option<String>,
pub highlighted_border_color: Option<String>, pub highlighted_border_color: Option<String>,
pub disabled_text_color: Option<String>,
pub text_color: Option<String>, pub text_color: Option<String>,
pub selected_text_color: Option<String>, pub selected_text_color: Option<String>,
pub selected_bg_color: Option<String>, pub selected_bg_color: Option<String>,
pub widget_title_color: Option<String>, pub widget_title_color: Option<String>,
pub graph_color: Option<String>, pub graph_color: Option<String>,
pub battery_colors: Option<Vec<String>>, pub high_battery_color: Option<String>,
pub medium_battery_color: Option<String>,
pub low_battery_color: Option<String>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
@ -803,7 +812,7 @@ fn get_disable_click(matches: &clap::ArgMatches<'static>, config: &Config) -> bo
false 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") { if matches.is_present("use_old_network_legend") {
return true; return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -814,7 +823,7 @@ pub fn get_use_old_network_legend(matches: &clap::ArgMatches<'static>, config: &
false 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") { if matches.is_present("hide_table_gap") {
return true; return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -825,7 +834,7 @@ pub fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config)
false 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") { if matches.is_present("battery") {
return true; return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -836,7 +845,7 @@ pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) ->
false 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") { if matches.is_present("no_write") {
return true; return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -847,7 +856,7 @@ pub fn get_no_write(matches: &clap::ArgMatches<'static>, config: &Config) -> boo
false false
} }
pub fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Filter>> { fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Filter>> {
if let Some(ignore_list) = ignore_list { if let Some(ignore_list) = ignore_list {
let list: Result<Vec<_>, _> = ignore_list let list: Result<Vec<_>, _> = ignore_list
.list .list
@ -888,3 +897,23 @@ pub fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option
Ok(None) Ok(None)
} }
} }
pub fn get_color_scheme(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<ColourScheme> {
if let Some(color) = matches.value_of("color") {
// Highest priority is always command line flags...
return ColourScheme::from_str(color);
} else if config.colors.is_some() {
// Then, give priority to custom colours...
return Ok(ColourScheme::Custom);
} else if let Some(flags) = &config.flags {
// Last priority is config file flags...
if let Some(color) = &flags.color {
return ColourScheme::from_str(color);
}
}
// And lastly, the final case is just "default".
Ok(ColourScheme::Default)
}

View file

@ -135,19 +135,6 @@ fn test_invalid_colour_string() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
#[test]
fn test_empty_battery() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/empty_battery.toml")
.assert()
.failure()
.stderr(predicate::str::contains(
"battery colour list must have at least one colour.",
));
Ok(())
}
#[test] #[test]
fn test_lone_default_widget_count() -> Result<(), Box<dyn std::error::Error>> { fn test_lone_default_widget_count() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_binary_location()) Command::new(get_binary_location())

View file

@ -1,2 +0,0 @@
[colors]
battery_colors=[]