refactor: refactor styling options (#1499)

Introduce a new configuration system for styling.
This commit is contained in:
Clement Tsang 2024-07-29 09:03:35 +00:00 committed by GitHub
parent e4eb69dbd5
commit 28972a1e64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 1763 additions and 1169 deletions

View file

@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `expanded_on_startup` is now `expanded`. - `expanded_on_startup` is now `expanded`.
- `left_legend` is now `cpu_left_legend`. - `left_legend` is now `cpu_left_legend`.
- [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following arguments have changed names: - [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following arguments have changed names:
- `mem_as_value` is now `process_memory_as_value`. - `--mem_as_value` is now `process_memory_as_value`.
- [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following config fields have changed names: - [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following config fields have changed names:
- `mem_as_value` is now `process_memory_as_value`. - `mem_as_value` is now `process_memory_as_value`.
- [#1481](https://github.com/ClementTsang/bottom/pull/1481): The following config fields have changed names: - [#1481](https://github.com/ClementTsang/bottom/pull/1481): The following config fields have changed names:
@ -33,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `mount_filter` is now `disk.mount_filter`. - `mount_filter` is now `disk.mount_filter`.
- `temp_filter` is now `temperature.sensor_filter` - `temp_filter` is now `temperature.sensor_filter`
- `net_filter` is now `network.interface_filter` - `net_filter` is now `network.interface_filter`
- [#1499](https://github.com/ClementTsang/bottom/pull/1499): Redesign how styling is configured.
- [#1499](https://github.com/ClementTsang/bottom/pull/1499): The following arguments have changed names:
- `--colors` is now `--theme`
### Bug Fixes ### Bug Fixes

View file

@ -86,7 +86,7 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
| Option | Behaviour | | Option | Behaviour |
| ------------------------ | ------------------------------------------ | | ------------------------ | ------------------------------------------ |
| `--color <COLOR SCHEME>` | Use a color scheme, use `--help` for info. | | `--theme <COLOR SCHEME>` | Use a color scheme, use `--help` for info. |
## Other Options ## Other Options

View file

@ -35,7 +35,6 @@ each time:
| `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. |
| `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). | | `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). |
| `process_memory_as_value` | Boolean | Defaults to showing process memory usage by value. | | `process_memory_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. |

View file

@ -0,0 +1,159 @@
# Styling
Various parts of the bottom can be styled, using either built-in themes or custom theming.
## Precedence
As there are a few ways styles can be applied to bottom, the order of which styles are prioritized are, in order of
highest precedence to lowest precedence:
1. Built-in themes set via command-line args (e.g. `btm --theme gruvbox`)
2. Custom themes set via config file
3. Built-in themes set via config file
If nothing is set, it will fall back to the default theme.
## Built-in styles
bottom has a few built-in themes:
- Default
- [Nord](https://www.nordtheme.com/)
- [Gruvbox](https://github.com/morhetz/gruvbox)
These themes all also have light variants to support terminals using lighter colours.
To set the theme from the command line:
```bash
btm --theme gruvbox
```
To set the theme using the config file:
```toml
[styles]
theme = "gruvbox"
```
## Custom styling
bottom's components can also be individually styled by the user to control the colour of the text style.
### Colours
You can configure the colours for components with strings that are either hex colours (e.g. `"#ffffff"`), RGB colours
(e.g. `"255, 255, 255"`), or named colours. Named colours are one of the following strings:
- `"Black"`
- `"Red"`
- `"Green"`
- `"Yellow"`
- `"Blue"`
- `"Magenta"`
- `"Cyan"`
- `"Gray"`
- `"DarkGray"`
- `"LightRed"`
- `"LightGreen"`
- `"LightYellow"`
- `"LightBlue"`
- `"LightMagenta"`
- `"LightCyan"`
- `"White"`
### Text
Text can generally be styled using the following TOML table:
```toml
[field]
# The foreground colour of text.
color = "black"
# The background colour of text.
bg_color = "blue"
# Whether to make the text bold.
bold = false
# Inline table version
field = { color = "black", bg_color = "blue", bold = false }
```
All fields are optional; by default if `bg_color` is not set then there will be no background color.
### Configuration
#### CPU
These can be set under `[styles.cpu]`:
| Config field | Details | Examples |
| ----------------- | ---------------------------------------------------------------- | -------------------------------------------- |
| `all_entry_color` | The colour of the "All" CPU label | `all_entry_color = "Red"` |
| `avg_entry_color` | The colour of the average CPU label and graph line | `avg_entry_color = "255, 0, 255"` |
| `cpu_core_colors` | Colour of each CPU threads' label and graph line. Read in order. | `cpu_core_colors = ["Red", "Blue", "Green"]` |
#### Memory
These can be set under `[styles.memory]`:
| Config field | Details | Examples |
| ------------ | ------------------------------------------------------------------------------ | --------------------------------- |
| `ram` | The colour of the RAM label and graph line | `ram = "Red"` |
| `cache` | The colour of the cache label and graph line. Does not do anything on Windows. | `cache = "#ffffff"` |
| `swap` | The colour of the swap label and graph line | `swap = "255, 0, 255"` |
| `arc` | The colour of the ARC label and graph line | `arc = "Blue"` |
| `gpus` | Colour of each GPU's memory label and graph line. Read in order. | `gpus = ["Red", "Blue", "Green"]` |
#### Network
These can be set under `[styles.network]`:
| Config field | Details | Examples |
| ------------ | --------------------------------------------------------- | ---------------------- |
| `rx` | The colour of the RX (download) label and graph line | `rx = "Red"` |
| `tx` | The colour of the TX (upload) label and graph line. | `tx = "#ffffff"` |
| `rx_total` | The colour of the total RX (download) label in basic mode | `rx_total = "0, 0, 0"` |
| `tx_total` | The colour of the total TX (upload) label in basic mode | `tx_total = "#000"` |
#### Battery
These can be set under `[styles.battery]`:
| Config field | Details | Examples |
| ---------------- | ------------------------------------------------------------------------ | ---------------------------- |
| `high_battery` | The colour of the battery widget bar when the battery is over 50% | `high_battery = "Red"` |
| `medium_battery` | The colour of the battery widget bar when the battery between 10% to 50% | `medium_battery = "#ffffff"` |
| `low_battery` | The colour of the battery widget bar when the battery is under 10% | `low_battery = "0, 0, 0"` |
#### Tables
These can be set under `[styles.tables]`:
| Config field | Details | Examples |
| ------------ | ------------------------------ | -------------------------------------------------------------- |
| `headers` | Text styling for table headers | `headers = { color = "red", bg_color = "black", bold = true }` |
#### Graphs
These can be set under `[styles.graphs]`:
| Config field | Details | Examples |
| ------------- | -------------------------------------------- | ------------------------------------------------------------------- |
| `graph_color` | The general colour of the parts of the graph | `graph_color = "white"` |
| `legend_text` | Text styling for graph's legend text | `legend_text = { color = "black", bg_color = "blue", bold = true }` |
#### General widget settings
These can be set under `[styles.widgets]`:
| Config field | Details | Examples |
| ----------------- | ------------------------------------------------------------ | --------------------------------------------------------------------- |
| `border` | The colour of the widgets' borders | `border = "white"` |
| `selected_border` | The colour of a widget's borders when the widget is selected | `selected_border = "white"` |
| `widget_title` | Text styling for a widget's title | `widget_title = { color = "black", bg_color = "blue", bold = true }` |
| `text` | Text styling for text in general | `text = { color = "black", bg_color = "blue", bold = true }` |
| `selected_text` | Text styling for text when representing something selected | `selected_text = { color = "black", bg_color = "blue", bold = true }` |
| `disabled_text` | Text styling for text when representing something disabled | `disabled_text = { color = "black", bg_color = "blue", bold = true }` |

View file

@ -1,33 +0,0 @@
# Theming
!!! Warning
This section is in progress, and is just copied from the old documentation.
The config file can be used to set custom colours for parts of the application under the `[colors]` object. The following labels are customizable with strings that are hex colours, RGB colours, or specific named colours.
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"` |
| 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"` |
| GPU colour per gpu | Colour of each gpu. Read in order. | `gpu_core_colors=["#ffffff", "white", "255, 255, 255"]` |
| ARC | The colour ARC will use | `arc_color="#ffffff"` |

View file

@ -106,23 +106,28 @@ If your configuration files aren't working, here are a few things to try:
### Check the formatting ### Check the formatting
It may be handy to refer to the automatically generated config files or the [sample configuration files](https://github.com/ClementTsang/bottom/tree/main/sample_configs). It may be handy to refer to the automatically generated config files or the
The config files also follow the [TOML](https://toml.io/en/) format. [sample configuration files](https://github.com/ClementTsang/bottom/tree/main/sample_configs). The config files also
follow the [TOML](https://toml.io/en/) format.
Also make sure your config options are under the right table - for example, to set your temperature type, you must set it under the `[flags]` table: Also make sure your config options are under the right table - for example, to set your temperature type, you must
set it under the `[flags]` table:
```toml ```toml
[flags] [flags]
temperature_type = "f" temperature_type = "f"
``` ```
Meanwhile, if you want to set a custom color scheme, it would be under the `[colors]` table: Meanwhile, if you want to set a custom color scheme, it would be under the `[styles]` table:
```toml ```toml
[colors] [styles.tables.headers]
table_header_color="LightBlue" color="LightBlue"
``` ```
To help validate your configuration files, there is [JSON Schema](https://json-schema.org/) support if your IDE/editor
supports it.
### Check the configuration file location ### Check the configuration file location
Make sure bottom is reading the right configuration file. By default, bottom looks for config files at these locations: Make sure bottom is reading the right configuration file. By default, bottom looks for config files at these locations:

View file

@ -166,7 +166,7 @@ nav:
- "Config File": - "Config File":
- configuration/config-file/index.md - configuration/config-file/index.md
- "Flags": configuration/config-file/flags.md - "Flags": configuration/config-file/flags.md
- "Theming": configuration/config-file/theming.md - "Styling": configuration/config-file/styling.md
- "Layout": configuration/config-file/layout.md - "Layout": configuration/config-file/layout.md
- "Data Filtering": configuration/config-file/data-filtering.md - "Data Filtering": configuration/config-file/data-filtering.md
- "Processes": configuration/config-file/processes.md - "Processes": configuration/config-file/processes.md

View file

@ -127,43 +127,57 @@
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
#[colors] # Uncomment if you want to use custom colors #[styles] # Uncomment if you want to use custom styling
# Represents the colour of table headers (processes, CPU, disks, temperature).
#table_header_color="LightBlue" # Built-in themes. Valid values are:
# Represents the colour of the label each widget has. # - "default"
#widget_title_color="Gray" # - "default-light"
# Represents the average CPU color. # - "gruvbox"
#avg_cpu_color="Red" # - "gruvbox-light"
# Represents the colour the core will use in the CPU legend and graph. # - "nord"
#cpu_core_colors=["LightMagenta", "LightYellow", "LightCyan", "LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"] # - "nord-light".
# Represents the colour RAM will use in the memory legend and graph. #
#ram_color="LightMagenta" # This will have the lowest precedence if a custom colour palette is set,
# Represents the colour SWAP will use in the memory legend and graph. # or overriden if the command-line flag for a built-in theme is set.
#swap_color="LightYellow" #theme = "default"
# Represents the colour ARC will use in the memory legend and graph.
#arc_color="LightCyan" #[styles.cpu]
# Represents the colour the GPU will use in the legend and graph. #all_entry_color = "green"
#gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"] #avg_entry_color = "red"
# Represents the colour rx will use in the network legend and graph. #cpu_core_colors = ["light magenta", "light yellow", "light cyan", "light green", "light blue", "cyan", "green", "blue"]
#rx_color="LightCyan"
# Represents the colour tx will use in the network legend and graph. #[styles.memory]
#tx_color="LightGreen" #ram = "light magenta"
# Represents the colour of the border of unselected widgets. #cache = "light red"
#border_color="Gray" #swap = "light yellow"
# Represents the colour of the border of selected widgets. #arc = "light cyan"
#highlighted_border_color="LightBlue" #gpus = ["light blue", "light red", "cyan", "green", "blue", "red"]
# Represents the colour of most text.
#text_color="Gray" #[styles.network]
# Represents the colour of text that is selected. #rx = "light magenta"
#selected_text_color="Black" #tx = "light yellow"
# Represents the background colour of text that is selected. #rx_total = "light cyan"
#selected_bg_color="LightBlue" #tx_total = "light green"
# Represents the colour of the lines and text of the graph.
#graph_color="Gray" #[styles.battery]
# Represents the colours of the battery based on charge #high_battery = "green"
#high_battery_color="green" #medium_battery = "yellow"
#medium_battery_color="yellow" #low_battery = "red"
#low_battery_color="red"
#[styles.tables]
#headers = {color = "light blue"}
#[styles.graphs]
#graph_color = "gray"
#legend_text = {color = "gray"}
#[styles.widgets]
#border = "gray"
#selected_border = "light blue"
#widget_title = {color = "gray"}
#text = {color = "gray"}
#selected_text = {color = "black", bg_color = "light blue"}
#disabled_text = {color = "dark gray"}
# Layout - layouts follow a pattern like this: # Layout - layouts follow a pattern like this:
# [[row]] represents a row in the application. # [[row]] represents a row in the application.

View file

@ -13,3 +13,6 @@ whole_word = false
regex = true regex = true
default_widget_type = "cpu" default_widget_type = "cpu"
default_widget_count = 1 default_widget_count = 1
[styles]
theme = "gruvbox"

View file

@ -5,16 +5,6 @@
"description": "https://clementtsang.github.io/bottom/nightly/configuration/config-file", "description": "https://clementtsang.github.io/bottom/nightly/configuration/config-file",
"type": "object", "type": "object",
"properties": { "properties": {
"colors": {
"anyOf": [
{
"$ref": "#/definitions/ColoursConfig"
},
{
"type": "null"
}
]
},
"cpu": { "cpu": {
"anyOf": [ "anyOf": [
{ {
@ -74,6 +64,16 @@
"$ref": "#/definitions/row" "$ref": "#/definitions/row"
} }
}, },
"styles": {
"anyOf": [
{
"$ref": "#/definitions/StyleConfig"
},
{
"type": "null"
}
]
},
"temperature": { "temperature": {
"anyOf": [ "anyOf": [
{ {
@ -86,161 +86,44 @@
} }
}, },
"definitions": { "definitions": {
"ColoursConfig": { "BatteryStyle": {
"description": "Colour configuration.", "description": "Styling specific to the battery widget.",
"type": "object", "type": "object",
"properties": { "properties": {
"all_cpu_color": { "high_battery": {
"type": [ "anyOf": [
"string", {
"null" "$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
] ]
}, },
"arc_color": { "low_battery": {
"type": [ "anyOf": [
"string", {
"null" "$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
] ]
}, },
"avg_cpu_color": { "medium_battery": {
"type": [ "anyOf": [
"string", {
"null" "$ref": "#/definitions/ColorStr"
]
}, },
"border_color": { {
"type": [ "type": "null"
"string", }
"null"
] ]
}
}
}, },
"cache_color": { "ColorStr": {
"type": [
"string",
"null"
]
},
"cpu_core_colors": {
"type": [
"array",
"null"
],
"items": {
"type": "string" "type": "string"
}
},
"disabled_text_color": {
"type": [
"string",
"null"
]
},
"gpu_core_colors": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"graph_color": {
"type": [
"string",
"null"
]
},
"high_battery_color": {
"type": [
"string",
"null"
]
},
"highlighted_border_color": {
"type": [
"string",
"null"
]
},
"low_battery_color": {
"type": [
"string",
"null"
]
},
"medium_battery_color": {
"type": [
"string",
"null"
]
},
"ram_color": {
"type": [
"string",
"null"
]
},
"rx_color": {
"type": [
"string",
"null"
]
},
"rx_total_color": {
"type": [
"string",
"null"
]
},
"selected_bg_color": {
"type": [
"string",
"null"
]
},
"selected_text_color": {
"type": [
"string",
"null"
]
},
"swap_color": {
"type": [
"string",
"null"
]
},
"table_header_color": {
"type": [
"string",
"null"
]
},
"text_color": {
"type": [
"string",
"null"
]
},
"tx_color": {
"type": [
"string",
"null"
]
},
"tx_total_color": {
"type": [
"string",
"null"
]
},
"widget_title_color": {
"type": [
"string",
"null"
]
}
}
}, },
"CpuConfig": { "CpuConfig": {
"description": "CPU column settings.", "description": "CPU column settings.",
@ -259,6 +142,41 @@
"average" "average"
] ]
}, },
"CpuStyle": {
"description": "Styling specific to the CPU widget.",
"type": "object",
"properties": {
"all_entry_color": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"avg_entry_color": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"cpu_core_colors": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/ColorStr"
}
}
}
},
"DiskConfig": { "DiskConfig": {
"description": "Disk configuration.", "description": "Disk configuration.",
"type": "object", "type": "object",
@ -340,13 +258,6 @@
"null" "null"
] ]
}, },
"color": {
"description": "For built-in colour palettes.",
"type": [
"string",
"null"
]
},
"cpu_left_legend": { "cpu_left_legend": {
"type": [ "type": [
"boolean", "boolean",
@ -565,6 +476,32 @@
} }
} }
}, },
"GraphStyle": {
"description": "General styling for graph widgets.",
"type": "object",
"properties": {
"graph_color": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"legend_text": {
"anyOf": [
{
"$ref": "#/definitions/TextStyleConfig"
},
{
"type": "null"
}
]
}
}
},
"IgnoreList": { "IgnoreList": {
"type": "object", "type": "object",
"required": [ "required": [
@ -595,6 +532,61 @@
} }
} }
}, },
"MemoryStyle": {
"description": "Styling specific to the memory widget.",
"type": "object",
"properties": {
"arc": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"cache": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"gpus": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/ColorStr"
}
},
"ram": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"swap": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
}
}
},
"NetworkConfig": { "NetworkConfig": {
"description": "Network configuration.", "description": "Network configuration.",
"type": "object", "type": "object",
@ -612,6 +604,54 @@
} }
} }
}, },
"NetworkStyle": {
"description": "Styling specific to the network widget.",
"type": "object",
"properties": {
"rx": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"rx_total": {
"description": "Set the colour of the \"rx total\" text. This only affects basic mode.",
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"tx": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"tx_total": {
"description": "Set the colour of the \"tx total\" text. This only affects basic mode.",
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
}
}
},
"ProcColumn": { "ProcColumn": {
"description": "A column in the process widget.", "description": "A column in the process widget.",
"type": "string", "type": "string",
@ -696,6 +736,112 @@
} }
] ]
}, },
"StyleConfig": {
"description": "Style-related configs.",
"type": "object",
"properties": {
"battery": {
"description": "Styling for the battery widget.",
"anyOf": [
{
"$ref": "#/definitions/BatteryStyle"
},
{
"type": "null"
}
]
},
"cpu": {
"description": "Styling for the CPU widget.",
"anyOf": [
{
"$ref": "#/definitions/CpuStyle"
},
{
"type": "null"
}
]
},
"graphs": {
"description": "Styling for graph widgets.",
"anyOf": [
{
"$ref": "#/definitions/GraphStyle"
},
{
"type": "null"
}
]
},
"memory": {
"description": "Styling for the memory widget.",
"anyOf": [
{
"$ref": "#/definitions/MemoryStyle"
},
{
"type": "null"
}
]
},
"network": {
"description": "Styling for the network widget.",
"anyOf": [
{
"$ref": "#/definitions/NetworkStyle"
},
{
"type": "null"
}
]
},
"tables": {
"description": "Styling for table widgets.",
"anyOf": [
{
"$ref": "#/definitions/TableStyle"
},
{
"type": "null"
}
]
},
"theme": {
"description": "A built-in theme.\n\nIf this is and a custom colour are both set, in the config file, the custom colour scheme will be prioritized first. If a theme is set in the command-line args, however, it will always be prioritized first.",
"type": [
"string",
"null"
]
},
"widgets": {
"description": "Styling for general widgets.",
"anyOf": [
{
"$ref": "#/definitions/WidgetStyle"
},
{
"type": "null"
}
]
}
}
},
"TableStyle": {
"description": "General styling for table widgets.",
"type": "object",
"properties": {
"headers": {
"anyOf": [
{
"$ref": "#/definitions/TextStyleConfig"
},
{
"type": "null"
}
]
}
}
},
"TempConfig": { "TempConfig": {
"description": "Temperature configuration.", "description": "Temperature configuration.",
"type": "object", "type": "object",
@ -713,6 +859,107 @@
} }
} }
}, },
"TextStyleConfig": {
"description": "A style for text.",
"type": "object",
"properties": {
"bg_color": {
"description": "A built-in ANSI colour, RGB hex, or RGB colour code.",
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"bold": {
"description": "Whether to make this text bolded or not. If not set, will default to built-in defaults.",
"type": [
"boolean",
"null"
]
},
"color": {
"description": "A built-in ANSI colour, RGB hex, or RGB colour code.",
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
}
}
},
"WidgetStyle": {
"description": "General styling for generic widgets.",
"type": "object",
"properties": {
"border": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"disabled_text": {
"anyOf": [
{
"$ref": "#/definitions/TextStyleConfig"
},
{
"type": "null"
}
]
},
"selected_border": {
"anyOf": [
{
"$ref": "#/definitions/ColorStr"
},
{
"type": "null"
}
]
},
"selected_text": {
"anyOf": [
{
"$ref": "#/definitions/TextStyleConfig"
},
{
"type": "null"
}
]
},
"text": {
"anyOf": [
{
"$ref": "#/definitions/TextStyleConfig"
},
{
"type": "null"
}
]
},
"widget_title": {
"anyOf": [
{
"$ref": "#/definitions/TextStyleConfig"
},
{
"type": "null"
}
]
}
}
},
"row": { "row": {
"description": "Represents a row. This has a length of some sort (optional) and a vector of children.", "description": "Represents a row. This has a length of some sort (optional) and a vector of children.",
"type": "object", "type": "object",

View file

@ -1529,16 +1529,14 @@ impl App {
} }
fn move_widget_selection_logic(&mut self, direction: &WidgetDirection) { fn move_widget_selection_logic(&mut self, direction: &WidgetDirection) {
/* // The actual logic for widget movement.
The actual logic for widget movement.
We follow these following steps: // We follow these following steps:
1. Send a movement signal in `direction`. // 1. Send a movement signal in `direction`.
2. Check if this new widget we've landed on is hidden. If not, halt. // 2. Check if this new widget we've landed on is hidden. If not, halt.
3. If it hidden, loop and either send: // 3. If it hidden, loop and either send:
- A signal equal to the current direction, if it is opposite of the reflection. // - A signal equal to the current direction, if it is opposite of the reflection.
- Reflection direction. // - Reflection direction.
*/
if !self.ignore_normal_keybinds() && !self.is_expanded { if !self.ignore_normal_keybinds() && !self.is_expanded {
if let Some(new_widget_id) = &(match direction { if let Some(new_widget_id) = &(match direction {

View file

@ -1,13 +1,9 @@
pub mod components; pub mod components;
mod dialogs; mod dialogs;
mod drawing_utils; mod drawing_utils;
pub mod styling;
mod widgets; mod widgets;
use std::str::FromStr;
use itertools::izip; use itertools::izip;
use styling::*;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -22,42 +18,12 @@ use crate::{
App, App,
}, },
constants::*, constants::*,
options::OptionError, options::config::style::ColourPalette,
}; };
#[derive(Debug)]
pub enum ColourScheme {
Default,
DefaultLight,
Gruvbox,
GruvboxLight,
Nord,
NordLight,
Custom,
}
impl FromStr for ColourScheme {
type Err = OptionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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),
"nord-light" => Ok(ColourScheme::NordLight),
_ => Err(OptionError::other(format!(
"'{s}' is an invalid built-in color scheme."
))),
}
}
}
/// Handles the canvas' state. /// Handles the canvas' state.
pub struct Painter { pub struct Painter {
pub colours: CanvasStyling, pub colours: ColourPalette,
previous_height: u16, previous_height: u16,
previous_width: u16, previous_width: u16,
@ -81,7 +47,7 @@ pub enum LayoutConstraint {
} }
impl Painter { impl Painter {
pub fn init(layout: BottomLayout, styling: CanvasStyling) -> anyhow::Result<Self> { pub fn init(layout: BottomLayout, styling: ColourPalette) -> 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
// based on the console size. // based on the console size.
@ -193,7 +159,7 @@ impl Painter {
f.render_widget( f.render_widget(
Paragraph::new(Span::styled( Paragraph::new(Span::styled(
"Frozen, press 'f' to unfreeze", "Frozen, press 'f' to unfreeze",
self.colours.currently_selected_text_style, self.colours.selected_text_style,
)), )),
Layout::default() Layout::default()
.horizontal_margin(1) .horizontal_margin(1)

View file

@ -1,6 +1,6 @@
use tui::style::Style; use tui::style::Style;
use crate::canvas::styling::CanvasStyling; use crate::options::config::style::ColourPalette;
#[derive(Default)] #[derive(Default)]
pub struct DataTableStyling { pub struct DataTableStyling {
@ -13,13 +13,13 @@ pub struct DataTableStyling {
} }
impl DataTableStyling { impl DataTableStyling {
pub fn from_colours(colours: &CanvasStyling) -> Self { pub fn from_palette(colours: &ColourPalette) -> Self {
Self { Self {
header_style: colours.table_header_style, header_style: colours.table_header_style,
border_style: colours.border_style, border_style: colours.border_style,
highlighted_border_style: colours.highlighted_border_style, highlighted_border_style: colours.highlighted_border_style,
text_style: colours.text_style, text_style: colours.text_style,
highlighted_text_style: colours.currently_selected_text_style, highlighted_text_style: colours.selected_text_style,
title_style: colours.widget_title_style, title_style: colours.widget_title_style,
} }
} }

View file

@ -211,12 +211,12 @@ impl Painter {
if MAX_PROCESS_SIGNAL == 1 || !app_state.app_config_fields.is_advanced_kill { if MAX_PROCESS_SIGNAL == 1 || !app_state.app_config_fields.is_advanced_kill {
let (yes_button, no_button) = match app_state.delete_dialog_state.selected_signal { let (yes_button, no_button) = match app_state.delete_dialog_state.selected_signal {
KillSignal::Kill(_) => ( KillSignal::Kill(_) => (
Span::styled("Yes", self.colours.currently_selected_text_style), Span::styled("Yes", self.colours.selected_text_style),
Span::styled("No", self.colours.text_style), Span::styled("No", self.colours.text_style),
), ),
KillSignal::Cancel => ( KillSignal::Cancel => (
Span::styled("Yes", self.colours.text_style), Span::styled("Yes", self.colours.text_style),
Span::styled("No", self.colours.currently_selected_text_style), Span::styled("No", self.colours.selected_text_style),
), ),
}; };
@ -325,10 +325,8 @@ impl Painter {
.map(|text| Span::styled(*text, self.colours.text_style)) .map(|text| Span::styled(*text, self.colours.text_style))
.collect::<Vec<Span<'_>>>(); .collect::<Vec<Span<'_>>>();
buttons.insert(0, Span::styled(SIGNAL_TEXT[0], self.colours.text_style)); buttons.insert(0, Span::styled(SIGNAL_TEXT[0], self.colours.text_style));
buttons[selected - scroll_offset] = Span::styled( buttons[selected - scroll_offset] =
SIGNAL_TEXT[selected], Span::styled(SIGNAL_TEXT[selected], self.colours.selected_text_style);
self.colours.currently_selected_text_style,
);
app_state.delete_dialog_state.button_positions = layout app_state.delete_dialog_state.button_positions = layout
.iter() .iter()

View file

@ -1,305 +0,0 @@
mod colour_utils;
use colour_utils::*;
use tui::style::{Color, Style};
use super::ColourScheme;
pub use crate::options::ConfigV1;
use crate::{
constants::*,
options::{colours::ColoursConfig, OptionError, OptionResult},
};
pub struct CanvasStyling {
pub currently_selected_text_colour: Color,
pub currently_selected_bg_colour: Color,
pub currently_selected_text_style: Style,
pub table_header_style: Style,
pub ram_style: Style,
#[cfg(not(target_os = "windows"))]
pub cache_style: Style,
pub swap_style: Style,
pub arc_style: Style,
pub gpu_colour_styles: Vec<Style>,
pub rx_style: Style,
pub tx_style: Style,
pub total_rx_style: Style,
pub total_tx_style: Style,
pub all_colour_style: Style,
pub avg_colour_style: Style,
pub cpu_colour_styles: Vec<Style>,
pub border_style: Style,
pub highlighted_border_style: Style,
pub text_style: Style,
pub widget_title_style: Style,
pub graph_style: Style,
pub high_battery_colour: Style,
pub medium_battery_colour: Style,
pub low_battery_colour: Style,
pub invalid_query_style: Style,
pub disabled_text_style: Style,
}
impl Default for CanvasStyling {
fn default() -> Self {
let text_colour = Color::Gray;
let currently_selected_text_colour = Color::Black;
let currently_selected_bg_colour = HIGHLIGHT_COLOUR;
CanvasStyling {
currently_selected_text_colour,
currently_selected_bg_colour,
currently_selected_text_style: Style::default()
.fg(currently_selected_text_colour)
.bg(currently_selected_bg_colour),
table_header_style: Style::default().fg(HIGHLIGHT_COLOUR),
ram_style: Style::default().fg(FIRST_COLOUR),
#[cfg(not(target_os = "windows"))]
cache_style: Style::default().fg(FIFTH_COLOUR),
swap_style: Style::default().fg(SECOND_COLOUR),
arc_style: Style::default().fg(THIRD_COLOUR),
gpu_colour_styles: vec![
Style::default().fg(FOURTH_COLOUR),
Style::default().fg(Color::LightBlue),
Style::default().fg(Color::LightRed),
Style::default().fg(Color::Cyan),
Style::default().fg(Color::Green),
Style::default().fg(Color::Blue),
Style::default().fg(Color::Red),
],
rx_style: Style::default().fg(FIRST_COLOUR),
tx_style: Style::default().fg(SECOND_COLOUR),
total_rx_style: Style::default().fg(THIRD_COLOUR),
total_tx_style: Style::default().fg(FOURTH_COLOUR),
all_colour_style: Style::default().fg(ALL_COLOUR),
avg_colour_style: Style::default().fg(AVG_COLOUR),
cpu_colour_styles: vec![
Style::default().fg(Color::LightMagenta),
Style::default().fg(Color::LightYellow),
Style::default().fg(Color::LightCyan),
Style::default().fg(Color::LightGreen),
Style::default().fg(Color::LightBlue),
Style::default().fg(Color::Cyan),
Style::default().fg(Color::Green),
Style::default().fg(Color::Blue),
],
border_style: Style::default().fg(text_colour),
highlighted_border_style: Style::default().fg(HIGHLIGHT_COLOUR),
text_style: Style::default().fg(text_colour),
widget_title_style: Style::default().fg(text_colour),
graph_style: Style::default().fg(text_colour),
high_battery_colour: Style::default().fg(Color::Green),
medium_battery_colour: Style::default().fg(Color::Yellow),
low_battery_colour: Style::default().fg(Color::Red),
invalid_query_style: Style::default().fg(Color::Red),
disabled_text_style: Style::default().fg(Color::DarkGray),
}
}
}
macro_rules! try_set_colour {
($field:expr, $colours:expr, $colour_field:ident) => {
if let Some(colour_str) = &$colours.$colour_field {
$field = str_to_fg(colour_str).map_err(|err| {
OptionError::config(format!(
"Please update 'colors.{}' in your config file. {err}",
stringify!($colour_field)
))
})?;
}
};
}
macro_rules! try_set_colour_list {
($field:expr, $colours:expr, $colour_field:ident) => {
if let Some(colour_list) = &$colours.$colour_field {
$field = colour_list
.iter()
.map(|s| str_to_fg(s))
.collect::<Result<Vec<Style>, String>>()
.map_err(|err| {
OptionError::config(format!(
"Please update 'colors.{}' in your config file. {err}",
stringify!($colour_field)
))
})?;
}
};
}
impl CanvasStyling {
pub fn new(colour_scheme: ColourScheme, config: &ConfigV1) -> anyhow::Result<Self> {
let mut canvas_colours = Self::default();
match colour_scheme {
ColourScheme::Default => {}
ColourScheme::DefaultLight => {
canvas_colours.set_colours_from_palette(&default_light_mode_colour_palette())?;
}
ColourScheme::Gruvbox => {
canvas_colours.set_colours_from_palette(&gruvbox_colour_palette())?;
}
ColourScheme::GruvboxLight => {
canvas_colours.set_colours_from_palette(&gruvbox_light_colour_palette())?;
}
ColourScheme::Nord => {
canvas_colours.set_colours_from_palette(&nord_colour_palette())?;
}
ColourScheme::NordLight => {
canvas_colours.set_colours_from_palette(&nord_light_colour_palette())?;
}
ColourScheme::Custom => {
if let Some(colors) = &config.colors {
canvas_colours.set_colours_from_palette(colors)?;
}
}
}
Ok(canvas_colours)
}
pub fn set_colours_from_palette(&mut self, colours: &ColoursConfig) -> OptionResult<()> {
// CPU
try_set_colour!(self.avg_colour_style, colours, avg_cpu_color);
try_set_colour!(self.all_colour_style, colours, all_cpu_color);
try_set_colour_list!(self.cpu_colour_styles, colours, cpu_core_colors);
// Memory
#[cfg(not(target_os = "windows"))]
try_set_colour!(self.cache_style, colours, cache_color);
#[cfg(feature = "zfs")]
try_set_colour!(self.arc_style, colours, arc_color);
#[cfg(feature = "gpu")]
try_set_colour_list!(self.gpu_colour_styles, colours, gpu_core_colors);
try_set_colour!(self.ram_style, colours, ram_color);
try_set_colour!(self.swap_style, colours, swap_color);
// Network
try_set_colour!(self.rx_style, colours, rx_color);
try_set_colour!(self.tx_style, colours, tx_color);
try_set_colour!(self.total_rx_style, colours, rx_total_color);
try_set_colour!(self.total_tx_style, colours, tx_total_color);
// Battery
try_set_colour!(self.high_battery_colour, colours, high_battery_color);
try_set_colour!(self.medium_battery_colour, colours, medium_battery_color);
try_set_colour!(self.low_battery_colour, colours, low_battery_color);
// Widget text and graphs
try_set_colour!(self.widget_title_style, colours, widget_title_color);
try_set_colour!(self.graph_style, colours, graph_color);
try_set_colour!(self.text_style, colours, text_color);
try_set_colour!(self.disabled_text_style, colours, disabled_text_color);
try_set_colour!(self.border_style, colours, border_color);
try_set_colour!(
self.highlighted_border_style,
colours,
highlighted_border_color
);
// Tables
try_set_colour!(self.table_header_style, colours, table_header_color);
if let Some(scroll_entry_text_color) = &colours.selected_text_color {
self.set_scroll_entry_text_color(scroll_entry_text_color)
.map_err(|err| {
OptionError::config(format!(
"Please update 'colors.selected_text_color' in your config file. {err}",
))
})?
}
if let Some(scroll_entry_bg_color) = &colours.selected_bg_color {
self.set_scroll_entry_bg_color(scroll_entry_bg_color)
.map_err(|err| {
OptionError::config(format!(
"Please update 'colors.selected_bg_color' in your config file. {err}",
))
})?
}
Ok(())
}
fn set_scroll_entry_text_color(&mut self, colour: &str) -> Result<(), String> {
self.currently_selected_text_colour = str_to_colour(colour)?;
self.currently_selected_text_style = Style::default()
.fg(self.currently_selected_text_colour)
.bg(self.currently_selected_bg_colour);
Ok(())
}
fn set_scroll_entry_bg_color(&mut self, colour: &str) -> Result<(), String> {
self.currently_selected_bg_colour = str_to_colour(colour)?;
self.currently_selected_text_style = Style::default()
.fg(self.currently_selected_text_colour)
.bg(self.currently_selected_bg_colour);
Ok(())
}
}
#[cfg(test)]
mod test {
use tui::style::{Color, Style};
use super::{CanvasStyling, ColourScheme};
use crate::options::ConfigV1;
#[test]
fn default_selected_colour_works() {
let mut colours = CanvasStyling::default();
assert_eq!(
colours.currently_selected_text_style,
Style::default()
.fg(colours.currently_selected_text_colour)
.bg(colours.currently_selected_bg_colour),
);
colours.set_scroll_entry_text_color("red").unwrap();
assert_eq!(
colours.currently_selected_text_style,
Style::default()
.fg(Color::Red)
.bg(colours.currently_selected_bg_colour),
);
colours.set_scroll_entry_bg_color("magenta").unwrap();
assert_eq!(
colours.currently_selected_text_style,
Style::default().fg(Color::Red).bg(Color::Magenta),
);
colours.set_scroll_entry_text_color("fake red").unwrap_err();
assert_eq!(
colours.currently_selected_text_style,
Style::default()
.fg(Color::Red)
.bg(colours.currently_selected_bg_colour),
);
colours
.set_scroll_entry_bg_color("fake magenta")
.unwrap_err();
assert_eq!(
colours.currently_selected_text_style,
Style::default().fg(Color::Red).bg(Color::Magenta),
);
}
#[test]
fn built_in_colour_schemes_work() {
let config = ConfigV1::default();
CanvasStyling::new(ColourScheme::Default, &config).unwrap();
CanvasStyling::new(ColourScheme::DefaultLight, &config).unwrap();
CanvasStyling::new(ColourScheme::Gruvbox, &config).unwrap();
CanvasStyling::new(ColourScheme::GruvboxLight, &config).unwrap();
CanvasStyling::new(ColourScheme::Nord, &config).unwrap();
CanvasStyling::new(ColourScheme::NordLight, &config).unwrap();
}
}

View file

@ -96,7 +96,7 @@ impl Painter {
) )
.divider(tui::symbols::line::VERTICAL) .divider(tui::symbols::line::VERTICAL)
.style(self.colours.text_style) .style(self.colours.text_style)
.highlight_style(self.colours.currently_selected_text_style) .highlight_style(self.colours.selected_text_style)
.select(battery_widget_state.currently_selected_battery_index), .select(battery_widget_state.currently_selected_battery_index),
tab_draw_loc, tab_draw_loc,
); );
@ -148,11 +148,11 @@ impl Painter {
])); ]));
battery_charge_rows.push(Row::new([Cell::from(bars).style( battery_charge_rows.push(Row::new([Cell::from(bars).style(
if charge_percentage < 10.0 { if charge_percentage < 10.0 {
self.colours.low_battery_colour self.colours.low_battery
} else if charge_percentage < 50.0 { } else if charge_percentage < 50.0 {
self.colours.medium_battery_colour self.colours.medium_battery
} else { } else {
self.colours.high_battery_colour self.colours.high_battery
}, },
)])); )]));

View file

@ -64,7 +64,7 @@ impl Painter {
last_entry, last_entry,
} => { } => {
let (outer, style) = match data_type { let (outer, style) = match data_type {
CpuDataType::Avg => ("AVG".to_string(), self.colours.avg_colour_style), CpuDataType::Avg => ("AVG".to_string(), self.colours.avg_cpu_colour),
CpuDataType::Cpu(index) => ( CpuDataType::Cpu(index) => (
format!("{index:<3}",), format!("{index:<3}",),
self.colours.cpu_colour_styles self.colours.cpu_colour_styles

View file

@ -136,9 +136,9 @@ impl Painter {
CpuWidgetData::All => None, CpuWidgetData::All => None,
CpuWidgetData::Entry { data, .. } => { CpuWidgetData::Entry { data, .. } => {
let style = if show_avg_cpu && itx == AVG_POSITION { let style = if show_avg_cpu && itx == AVG_POSITION {
self.colours.avg_colour_style self.colours.avg_cpu_colour
} else if itx == ALL_POSITION { } else if itx == ALL_POSITION {
self.colours.all_colour_style self.colours.all_cpu_colour
} else { } else {
let offset_position = itx - 1; // Because of the all position let offset_position = itx - 1; // Because of the all position
self.colours.cpu_colour_styles[(offset_position - show_avg_offset) self.colours.cpu_colour_styles[(offset_position - show_avg_offset)
@ -158,7 +158,7 @@ impl Painter {
cpu_data.get(current_scroll_position) cpu_data.get(current_scroll_position)
{ {
let style = if show_avg_cpu && current_scroll_position == AVG_POSITION { let style = if show_avg_cpu && current_scroll_position == AVG_POSITION {
self.colours.avg_colour_style self.colours.avg_cpu_colour
} else { } else {
let offset_position = current_scroll_position - 1; // Because of the all position let offset_position = current_scroll_position - 1; // Because of the all position
self.colours.cpu_colour_styles self.colours.cpu_colour_styles

View file

@ -133,7 +133,7 @@ impl Painter {
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
{ {
if let Some(gpu_data) = &app_state.converted_data.gpu_data { if let Some(gpu_data) = &app_state.converted_data.gpu_data {
let gpu_styles = &self.colours.gpu_colour_styles; let gpu_styles = &self.colours.gpu_colours;
let mut color_index = 0; let mut color_index = 0;
gpu_data.iter().for_each(|gpu_data_vec| { gpu_data.iter().for_each(|gpu_data_vec| {

View file

@ -89,7 +89,7 @@ impl Painter {
{ {
if let Some(gpu_data) = &app_state.converted_data.gpu_data { if let Some(gpu_data) = &app_state.converted_data.gpu_data {
let mut color_index = 0; let mut color_index = 0;
let gpu_styles = &self.colours.gpu_colour_styles; let gpu_styles = &self.colours.gpu_colours;
gpu_data.iter().for_each(|gpu| { gpu_data.iter().for_each(|gpu| {
let gpu_label = let gpu_label =
format!("{}:{}{}", gpu.name, gpu.mem_percent, gpu.mem_total); format!("{}:{}{}", gpu.name, gpu.mem_percent, gpu.mem_total);

View file

@ -183,7 +183,7 @@ impl Painter {
&proc_widget_state.proc_search.search_state, &proc_widget_state.proc_search.search_state,
available_width, available_width,
is_on_widget, is_on_widget,
self.colours.currently_selected_text_style, self.colours.selected_text_style,
self.colours.text_style, self.colours.text_style,
); );
@ -203,19 +203,19 @@ impl Painter {
// Text options shamelessly stolen from VS Code. // Text options shamelessly stolen from VS Code.
let case_style = if !proc_widget_state.proc_search.is_ignoring_case { let case_style = if !proc_widget_state.proc_search.is_ignoring_case {
self.colours.currently_selected_text_style self.colours.selected_text_style
} else { } else {
self.colours.text_style self.colours.text_style
}; };
let whole_word_style = if proc_widget_state.proc_search.is_searching_whole_word { let whole_word_style = if proc_widget_state.proc_search.is_searching_whole_word {
self.colours.currently_selected_text_style self.colours.selected_text_style
} else { } else {
self.colours.text_style self.colours.text_style
}; };
let regex_style = if proc_widget_state.proc_search.is_searching_with_regex { let regex_style = if proc_widget_state.proc_search.is_searching_with_regex {
self.colours.currently_selected_text_style self.colours.selected_text_style
} else { } else {
self.colours.text_style self.colours.text_style
}; };

View file

@ -1,7 +1,5 @@
use tui::widgets::Borders; use tui::widgets::Borders;
use crate::options::ColoursConfig;
// Default widget ID // Default widget ID
pub const DEFAULT_WIDGET_ID: u64 = 56709; pub const DEFAULT_WIDGET_ID: u64 = 56709;
@ -24,260 +22,6 @@ pub const TIME_LABEL_HEIGHT_LIMIT: u16 = 7;
// Side borders // Side borders
pub const SIDE_BORDERS: Borders = Borders::LEFT.union(Borders::RIGHT); pub const SIDE_BORDERS: Borders = Borders::LEFT.union(Borders::RIGHT);
// Colour profiles
// TODO: Generate these with a macro or something...
pub fn default_light_mode_colour_palette() -> ColoursConfig {
ColoursConfig {
text_color: Some("black".into()),
border_color: Some("black".into()),
table_header_color: Some("black".into()),
widget_title_color: Some("black".into()),
selected_text_color: Some("white".into()),
graph_color: Some("black".into()),
disabled_text_color: Some("gray".into()),
ram_color: Some("blue".into()),
#[cfg(not(target_os = "windows"))]
cache_color: Some("LightRed".into()),
swap_color: Some("red".into()),
arc_color: Some("LightBlue".into()),
gpu_core_colors: Some(vec![
"LightGreen".into(),
"LightCyan".into(),
"LightRed".into(),
"Cyan".into(),
"Green".into(),
"Blue".into(),
"Red".into(),
]),
rx_color: Some("blue".into()),
tx_color: Some("red".into()),
rx_total_color: Some("LightBlue".into()),
tx_total_color: Some("LightRed".into()),
cpu_core_colors: Some(vec![
"LightMagenta".into(),
"LightBlue".into(),
"LightRed".into(),
"Cyan".into(),
"Green".into(),
"Blue".into(),
"Red".into(),
]),
..ColoursConfig::default()
}
}
pub fn gruvbox_colour_palette() -> ColoursConfig {
ColoursConfig {
table_header_color: Some("#83a598".into()),
all_cpu_color: Some("#8ec07c".into()),
avg_cpu_color: Some("#fb4934".into()),
cpu_core_colors: Some(vec![
"#cc241d".into(),
"#98971a".into(),
"#d79921".into(),
"#458588".into(),
"#b16286".into(),
"#689d6a".into(),
"#fe8019".into(),
"#b8bb26".into(),
"#fabd2f".into(),
"#83a598".into(),
"#d3869b".into(),
"#d65d0e".into(),
"#9d0006".into(),
"#79740e".into(),
"#b57614".into(),
"#076678".into(),
"#8f3f71".into(),
"#427b58".into(),
"#d65d03".into(),
"#af3a03".into(),
]),
ram_color: Some("#8ec07c".into()),
#[cfg(not(target_os = "windows"))]
cache_color: Some("#b16286".into()),
swap_color: Some("#fabd2f".into()),
arc_color: Some("#689d6a".into()),
gpu_core_colors: Some(vec![
"#d79921".into(),
"#458588".into(),
"#b16286".into(),
"#fe8019".into(),
"#b8bb26".into(),
"#cc241d".into(),
"#98971a".into(),
]),
rx_color: Some("#8ec07c".into()),
tx_color: Some("#fabd2f".into()),
rx_total_color: Some("#689d6a".into()),
tx_total_color: Some("#d79921".into()),
border_color: Some("#ebdbb2".into()),
highlighted_border_color: Some("#fe8019".into()),
disabled_text_color: Some("#665c54".into()),
text_color: Some("#ebdbb2".into()),
selected_text_color: Some("#1d2021".into()),
selected_bg_color: Some("#ebdbb2".into()),
widget_title_color: Some("#ebdbb2".into()),
graph_color: Some("#ebdbb2".into()),
high_battery_color: Some("#98971a".into()),
medium_battery_color: Some("#fabd2f".into()),
low_battery_color: Some("#fb4934".into()),
}
}
pub fn gruvbox_light_colour_palette() -> ColoursConfig {
ColoursConfig {
table_header_color: Some("#076678".into()),
all_cpu_color: Some("#8ec07c".into()),
avg_cpu_color: Some("#fb4934".into()),
cpu_core_colors: Some(vec![
"#cc241d".into(),
"#98971a".into(),
"#d79921".into(),
"#458588".into(),
"#b16286".into(),
"#689d6a".into(),
"#fe8019".into(),
"#b8bb26".into(),
"#fabd2f".into(),
"#83a598".into(),
"#d3869b".into(),
"#d65d0e".into(),
"#9d0006".into(),
"#79740e".into(),
"#b57614".into(),
"#076678".into(),
"#8f3f71".into(),
"#427b58".into(),
"#d65d03".into(),
"#af3a03".into(),
]),
ram_color: Some("#427b58".into()),
#[cfg(not(target_os = "windows"))]
cache_color: Some("#d79921".into()),
swap_color: Some("#cc241d".into()),
arc_color: Some("#689d6a".into()),
gpu_core_colors: Some(vec![
"#9d0006".into(),
"#98971a".into(),
"#d79921".into(),
"#458588".into(),
"#b16286".into(),
"#fe8019".into(),
"#b8bb26".into(),
]),
rx_color: Some("#427b58".into()),
tx_color: Some("#cc241d".into()),
rx_total_color: Some("#689d6a".into()),
tx_total_color: Some("#9d0006".into()),
border_color: Some("#3c3836".into()),
highlighted_border_color: Some("#af3a03".into()),
disabled_text_color: Some("#d5c4a1".into()),
text_color: Some("#3c3836".into()),
selected_text_color: Some("#ebdbb2".into()),
selected_bg_color: Some("#3c3836".into()),
widget_title_color: Some("#3c3836".into()),
graph_color: Some("#3c3836".into()),
high_battery_color: Some("#98971a".into()),
medium_battery_color: Some("#d79921".into()),
low_battery_color: Some("#cc241d".into()),
}
}
pub fn nord_colour_palette() -> ColoursConfig {
ColoursConfig {
table_header_color: Some("#81a1c1".into()),
all_cpu_color: Some("#88c0d0".into()),
avg_cpu_color: Some("#8fbcbb".into()),
cpu_core_colors: Some(vec![
"#5e81ac".into(),
"#81a1c1".into(),
"#d8dee9".into(),
"#b48ead".into(),
"#a3be8c".into(),
"#ebcb8b".into(),
"#d08770".into(),
"#bf616a".into(),
]),
ram_color: Some("#88c0d0".into()),
#[cfg(not(target_os = "windows"))]
cache_color: Some("#d8dee9".into()),
swap_color: Some("#d08770".into()),
arc_color: Some("#5e81ac".into()),
gpu_core_colors: Some(vec![
"#8fbcbb".into(),
"#81a1c1".into(),
"#d8dee9".into(),
"#b48ead".into(),
"#a3be8c".into(),
"#ebcb8b".into(),
"#bf616a".into(),
]),
rx_color: Some("#88c0d0".into()),
tx_color: Some("#d08770".into()),
rx_total_color: Some("#5e81ac".into()),
tx_total_color: Some("#8fbcbb".into()),
border_color: Some("#88c0d0".into()),
highlighted_border_color: Some("#5e81ac".into()),
disabled_text_color: Some("#4c566a".into()),
text_color: Some("#e5e9f0".into()),
selected_text_color: Some("#2e3440".into()),
selected_bg_color: Some("#88c0d0".into()),
widget_title_color: Some("#e5e9f0".into()),
graph_color: Some("#e5e9f0".into()),
high_battery_color: Some("#a3be8c".into()),
medium_battery_color: Some("#ebcb8b".into()),
low_battery_color: Some("#bf616a".into()),
}
}
pub fn nord_light_colour_palette() -> ColoursConfig {
ColoursConfig {
table_header_color: Some("#5e81ac".into()),
all_cpu_color: Some("#81a1c1".into()),
avg_cpu_color: Some("#8fbcbb".into()),
cpu_core_colors: Some(vec![
"#5e81ac".into(),
"#88c0d0".into(),
"#4c566a".into(),
"#b48ead".into(),
"#a3be8c".into(),
"#ebcb8b".into(),
"#d08770".into(),
"#bf616a".into(),
]),
ram_color: Some("#81a1c1".into()),
#[cfg(not(target_os = "windows"))]
cache_color: Some("#4c566a".into()),
swap_color: Some("#d08770".into()),
arc_color: Some("#5e81ac".into()),
gpu_core_colors: Some(vec![
"#8fbcbb".into(),
"#88c0d0".into(),
"#4c566a".into(),
"#b48ead".into(),
"#a3be8c".into(),
"#ebcb8b".into(),
"#bf616a".into(),
]),
rx_color: Some("#81a1c1".into()),
tx_color: Some("#d08770".into()),
rx_total_color: Some("#5e81ac".into()),
tx_total_color: Some("#8fbcbb".into()),
border_color: Some("#2e3440".into()),
highlighted_border_color: Some("#5e81ac".into()),
disabled_text_color: Some("#d8dee9".into()),
text_color: Some("#2e3440".into()),
selected_text_color: Some("#f5f5f5".into()),
selected_bg_color: Some("#5e81ac".into()),
widget_title_color: Some("#2e3440".into()),
graph_color: Some("#2e3440".into()),
high_battery_color: Some("#a3be8c".into()),
medium_battery_color: Some("#ebcb8b".into()),
low_battery_color: Some("#bf616a".into()),
}
}
// Help text // Help text
pub const HELP_CONTENTS_TEXT: [&str; 10] = [ pub const HELP_CONTENTS_TEXT: [&str; 10] = [
"Either scroll or press the number key to go to the corresponding help menu section:", "Either scroll or press the number key to go to the corresponding help menu section:",
@ -573,8 +317,6 @@ 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"
#color = "default"
# Show memory values in the processes widget as values by default # Show memory values in the processes widget as values by default
#process_memory_as_value = false #process_memory_as_value = false
# Show tree mode by default in the processes widget. # Show tree mode by default in the processes widget.
@ -649,43 +391,57 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
#[colors] # Uncomment if you want to use custom colors #[styles] # Uncomment if you want to use custom styling
# Represents the colour of table headers (processes, CPU, disks, temperature).
#table_header_color="LightBlue" # Built-in themes. Valid values are:
# Represents the colour of the label each widget has. # - "default"
#widget_title_color="Gray" # - "default-light"
# Represents the average CPU color. # - "gruvbox"
#avg_cpu_color="Red" # - "gruvbox-light"
# Represents the colour the core will use in the CPU legend and graph. # - "nord"
#cpu_core_colors=["LightMagenta", "LightYellow", "LightCyan", "LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"] # - "nord-light".
# Represents the colour RAM will use in the memory legend and graph. #
#ram_color="LightMagenta" # This will have the lowest precedence if a custom colour palette is set,
# Represents the colour SWAP will use in the memory legend and graph. # or overriden if the command-line flag for a built-in theme is set.
#swap_color="LightYellow" #theme = "default"
# Represents the colour ARC will use in the memory legend and graph.
#arc_color="LightCyan" #[styles.cpu]
# Represents the colour the GPU will use in the legend and graph. #all_entry_color = "green"
#gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"] #avg_entry_color = "red"
# Represents the colour rx will use in the network legend and graph. #cpu_core_colors = ["light magenta", "light yellow", "light cyan", "light green", "light blue", "cyan", "green", "blue"]
#rx_color="LightCyan"
# Represents the colour tx will use in the network legend and graph. #[styles.memory]
#tx_color="LightGreen" #ram = "light magenta"
# Represents the colour of the border of unselected widgets. #cache = "light red"
#border_color="Gray" #swap = "light yellow"
# Represents the colour of the border of selected widgets. #arc = "light cyan"
#highlighted_border_color="LightBlue" #gpus = ["light blue", "light red", "cyan", "green", "blue", "red"]
# Represents the colour of most text.
#text_color="Gray" #[styles.network]
# Represents the colour of text that is selected. #rx = "light magenta"
#selected_text_color="Black" #tx = "light yellow"
# Represents the background colour of text that is selected. #rx_total = "light cyan"
#selected_bg_color="LightBlue" #tx_total = "light green"
# Represents the colour of the lines and text of the graph.
#graph_color="Gray" #[styles.battery]
# Represents the colours of the battery based on charge #high_battery = "green"
#high_battery_color="green" #medium_battery = "yellow"
#medium_battery_color="yellow" #low_battery = "red"
#low_battery_color="red"
#[styles.tables]
#headers = {color = "light blue"}
#[styles.graphs]
#graph_color = "gray"
#legend_text = {color = "gray"}
#[styles.widgets]
#border = "gray"
#selected_border = "light blue"
#widget_title = {color = "gray"}
#text = {color = "gray"}
#selected_text = {color = "black", bg_color = "light blue"}
#disabled_text = {color = "dark gray"}
# Layout - layouts follow a pattern like this: # Layout - layouts follow a pattern like this:
# [[row]] represents a row in the application. # [[row]] represents a row in the application.

View file

@ -37,7 +37,6 @@ use std::{
use anyhow::Context; use anyhow::Context;
use app::{layout_manager::UsedWidgets, App, AppConfigFields, DataFilters}; use app::{layout_manager::UsedWidgets, App, AppConfigFields, DataFilters};
use canvas::styling::CanvasStyling;
use crossterm::{ use crossterm::{
event::{ event::{
poll, read, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, poll, read, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste,
@ -49,7 +48,7 @@ use crossterm::{
}; };
use data_conversion::*; use data_conversion::*;
use event::{handle_key_event_or_break, handle_mouse_event, BottomEvent, CollectionThreadEvent}; use event::{handle_key_event_or_break, handle_mouse_event, BottomEvent, CollectionThreadEvent};
use options::{args, get_color_scheme, get_config_path, get_or_create_config, init_app}; use options::{args, get_config_path, get_or_create_config, init_app};
use tui::{backend::CrosstermBackend, Terminal}; use tui::{backend::CrosstermBackend, Terminal};
#[allow(unused_imports)] #[allow(unused_imports)]
use utils::logging::*; use utils::logging::*;
@ -278,7 +277,7 @@ fn create_collection_thread(
#[cfg(feature = "generate_schema")] #[cfg(feature = "generate_schema")]
fn generate_schema() -> anyhow::Result<()> { fn generate_schema() -> anyhow::Result<()> {
let mut schema = schemars::schema_for!(crate::options::config::ConfigV1); let mut schema = schemars::schema_for!(crate::options::config::Config);
{ {
use itertools::Itertools; use itertools::Itertools;
use strum::VariantArray; use strum::VariantArray;
@ -336,15 +335,8 @@ fn main() -> anyhow::Result<()> {
.context("Unable to parse or create the config file.")? .context("Unable to parse or create the config file.")?
}; };
// FIXME: Should move this into build app or config // Create the "app" and initialize a bunch of stuff.
let styling = { let (mut app, widget_layout, styling) = init_app(args, config)?;
let colour_scheme = get_color_scheme(&args, &config)?;
CanvasStyling::new(colour_scheme, &config)?
};
// Create an "app" struct, which will control most of the program and store
// settings/state
let (mut app, widget_layout) = init_app(args, config, &styling)?;
// Create painter and set colours. // Create painter and set colours.
let mut painter = canvas::Painter::init(widget_layout, styling)?; let mut painter = canvas::Painter::init(widget_layout, styling)?;

View file

@ -3,7 +3,6 @@
// TODO: Break this apart or do something a bit smarter. // TODO: Break this apart or do something a bit smarter.
pub mod args; pub mod args;
pub mod colours;
pub mod config; pub mod config;
mod error; mod error;
@ -17,8 +16,8 @@ use std::{
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
pub use colours::ColoursConfig; use config::style::ColourPalette;
pub use config::ConfigV1; pub use config::Config;
pub(crate) use error::{OptionError, OptionResult}; pub(crate) use error::{OptionError, OptionResult};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use indexmap::IndexSet; use indexmap::IndexSet;
@ -32,7 +31,7 @@ use self::{
}; };
use crate::{ use crate::{
app::{filter::Filter, layout_manager::*, *}, app::{filter::Filter, layout_manager::*, *},
canvas::{components::time_chart::LegendPosition, styling::CanvasStyling, ColourScheme}, canvas::components::time_chart::LegendPosition,
constants::*, constants::*,
data_collection::temperature::TemperatureType, data_collection::temperature::TemperatureType,
utils::data_units::DataUnit, utils::data_units::DataUnit,
@ -97,7 +96,7 @@ pub fn get_config_path(override_config_path: Option<&Path>) -> Option<PathBuf> {
/// path, it will try to create a new file with the default settings, and return /// path, it will try to create a new file with the default settings, and return
/// the default config. If bottom fails to write a new config, it will silently /// the default config. If bottom fails to write a new config, it will silently
/// just return the default config. /// just return the default config.
pub fn get_or_create_config(config_path: Option<&Path>) -> OptionResult<ConfigV1> { pub fn get_or_create_config(config_path: Option<&Path>) -> OptionResult<Config> {
match &config_path { match &config_path {
Some(path) => { Some(path) => {
if let Ok(config_string) = fs::read_to_string(path) { if let Ok(config_string) = fs::read_to_string(path) {
@ -108,7 +107,7 @@ pub fn get_or_create_config(config_path: Option<&Path>) -> OptionResult<ConfigV1
} }
fs::File::create(path)?.write_all(CONFIG_TEXT.as_bytes())?; fs::File::create(path)?.write_all(CONFIG_TEXT.as_bytes())?;
Ok(ConfigV1::default()) Ok(Config::default())
} }
} }
None => { None => {
@ -116,14 +115,14 @@ pub fn get_or_create_config(config_path: Option<&Path>) -> OptionResult<ConfigV1
// but don't write to any file. // but don't write to any file.
// //
// TODO: Maybe make this "show" an error, but don't crash. // TODO: Maybe make this "show" an error, but don't crash.
Ok(ConfigV1::default()) Ok(Config::default())
} }
} }
} }
pub fn init_app( pub(crate) fn init_app(
args: BottomArgs, config: ConfigV1, styling: &CanvasStyling, args: BottomArgs, config: Config,
) -> Result<(App, BottomLayout)> { ) -> Result<(App, BottomLayout, ColourPalette)> {
use BottomWidgetType::*; use BottomWidgetType::*;
// Since everything takes a reference, but we want to take ownership here to // Since everything takes a reference, but we want to take ownership here to
@ -131,6 +130,8 @@ pub fn init_app(
let args = &args; let args = &args;
let config = &config; let config = &config;
let styling = ColourPalette::new(args, config)?;
let (widget_layout, default_widget_id, default_widget_type_option) = let (widget_layout, default_widget_id, default_widget_type_option) =
get_widget_layout(args, config) get_widget_layout(args, config)
.context("Found an issue while trying to build the widget layout.")?; .context("Found an issue while trying to build the widget layout.")?;
@ -285,7 +286,7 @@ pub fn init_app(
.unwrap_or_default(), .unwrap_or_default(),
default_time_value, default_time_value,
autohide_timer, autohide_timer,
styling, &styling,
), ),
); );
} }
@ -318,7 +319,7 @@ pub fn init_app(
&app_config_fields, &app_config_fields,
mode, mode,
table_config, table_config,
styling, &styling,
&proc_columns, &proc_columns,
), ),
); );
@ -326,13 +327,13 @@ pub fn init_app(
Disk => { Disk => {
disk_state_map.insert( disk_state_map.insert(
widget.widget_id, widget.widget_id,
DiskTableWidget::new(&app_config_fields, styling), DiskTableWidget::new(&app_config_fields, &styling),
); );
} }
Temp => { Temp => {
temp_state_map.insert( temp_state_map.insert(
widget.widget_id, widget.widget_id,
TempWidgetState::new(&app_config_fields, styling), TempWidgetState::new(&app_config_fields, &styling),
); );
} }
Battery => { Battery => {
@ -439,11 +440,12 @@ pub fn init_app(
is_expanded, is_expanded,
), ),
widget_layout, widget_layout,
styling,
)) ))
} }
pub fn get_widget_layout( pub fn get_widget_layout(
args: &BottomArgs, config: &ConfigV1, args: &BottomArgs, config: &Config,
) -> OptionResult<(BottomLayout, u64, Option<BottomWidgetType>)> { ) -> OptionResult<(BottomLayout, u64, Option<BottomWidgetType>)> {
let cpu_left_legend = is_flag_enabled!(cpu_left_legend, args.cpu, config); let cpu_left_legend = is_flag_enabled!(cpu_left_legend, args.cpu, config);
@ -461,7 +463,7 @@ pub fn get_widget_layout(
Some(r) => r, Some(r) => r,
None => { None => {
// This cannot (like it really shouldn't) fail! // This cannot (like it really shouldn't) fail!
ref_row = toml_edit::de::from_str::<ConfigV1>(if get_use_battery(args, config) { ref_row = toml_edit::de::from_str::<Config>(if get_use_battery(args, config) {
DEFAULT_BATTERY_LAYOUT DEFAULT_BATTERY_LAYOUT
} else { } else {
DEFAULT_LAYOUT DEFAULT_LAYOUT
@ -592,7 +594,7 @@ macro_rules! parse_ms_option {
} }
#[inline] #[inline]
fn get_update_rate(args: &BottomArgs, config: &ConfigV1) -> OptionResult<u64> { fn get_update_rate(args: &BottomArgs, config: &Config) -> OptionResult<u64> {
parse_ms_option!( parse_ms_option!(
&args.general.rate, &args.general.rate,
config.flags.as_ref().and_then(|flags| flags.rate.as_ref()), config.flags.as_ref().and_then(|flags| flags.rate.as_ref()),
@ -603,7 +605,7 @@ fn get_update_rate(args: &BottomArgs, config: &ConfigV1) -> OptionResult<u64> {
) )
} }
fn get_temperature(args: &BottomArgs, config: &ConfigV1) -> OptionResult<TemperatureType> { fn get_temperature(args: &BottomArgs, config: &Config) -> OptionResult<TemperatureType> {
if args.temperature.fahrenheit { if args.temperature.fahrenheit {
return Ok(TemperatureType::Fahrenheit); return Ok(TemperatureType::Fahrenheit);
} else if args.temperature.kelvin { } else if args.temperature.kelvin {
@ -619,7 +621,7 @@ fn get_temperature(args: &BottomArgs, config: &ConfigV1) -> OptionResult<Tempera
} }
/// Yes, this function gets whether to show average CPU (true) or not (false). /// Yes, this function gets whether to show average CPU (true) or not (false).
fn get_show_average_cpu(args: &BottomArgs, config: &ConfigV1) -> bool { fn get_show_average_cpu(args: &BottomArgs, config: &Config) -> bool {
if args.cpu.hide_avg_cpu { if args.cpu.hide_avg_cpu {
return false; return false;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -633,7 +635,7 @@ fn get_show_average_cpu(args: &BottomArgs, config: &ConfigV1) -> bool {
#[inline] #[inline]
fn get_default_time_value( fn get_default_time_value(
args: &BottomArgs, config: &ConfigV1, retention_ms: u64, args: &BottomArgs, config: &Config, retention_ms: u64,
) -> OptionResult<u64> { ) -> OptionResult<u64> {
parse_ms_option!( parse_ms_option!(
&args.general.default_time_value, &args.general.default_time_value,
@ -649,7 +651,7 @@ fn get_default_time_value(
} }
#[inline] #[inline]
fn get_time_interval(args: &BottomArgs, config: &ConfigV1, retention_ms: u64) -> OptionResult<u64> { fn get_time_interval(args: &BottomArgs, config: &Config, retention_ms: u64) -> OptionResult<u64> {
parse_ms_option!( parse_ms_option!(
&args.general.time_delta, &args.general.time_delta,
config config
@ -664,7 +666,7 @@ fn get_time_interval(args: &BottomArgs, config: &ConfigV1, retention_ms: u64) ->
} }
fn get_default_widget_and_count( fn get_default_widget_and_count(
args: &BottomArgs, config: &ConfigV1, args: &BottomArgs, config: &Config,
) -> OptionResult<(Option<BottomWidgetType>, u64)> { ) -> OptionResult<(Option<BottomWidgetType>, u64)> {
let widget_type = if let Some(widget_type) = &args.general.default_widget_type { let widget_type = if let Some(widget_type) = &args.general.default_widget_type {
let parsed_widget = parse_arg_value!(widget_type.parse(), "default_widget_type")?; let parsed_widget = parse_arg_value!(widget_type.parse(), "default_widget_type")?;
@ -714,7 +716,7 @@ fn get_default_widget_and_count(
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_use_battery(args: &BottomArgs, config: &ConfigV1) -> bool { fn get_use_battery(args: &BottomArgs, config: &Config) -> bool {
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
{ {
// TODO: Move this so it's dynamic in the app itself and automatically hide if // TODO: Move this so it's dynamic in the app itself and automatically hide if
@ -740,7 +742,7 @@ fn get_use_battery(args: &BottomArgs, config: &ConfigV1) -> bool {
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_enable_gpu(args: &BottomArgs, config: &ConfigV1) -> bool { fn get_enable_gpu(args: &BottomArgs, config: &Config) -> bool {
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
{ {
if args.gpu.enable_gpu { if args.gpu.enable_gpu {
@ -756,7 +758,7 @@ fn get_enable_gpu(args: &BottomArgs, config: &ConfigV1) -> bool {
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_enable_cache_memory(args: &BottomArgs, config: &ConfigV1) -> bool { fn get_enable_cache_memory(args: &BottomArgs, config: &Config) -> bool {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
if args.memory.enable_cache_memory { if args.memory.enable_cache_memory {
@ -810,32 +812,7 @@ fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> OptionResult<Option<Filt
} }
} }
pub fn get_color_scheme(args: &BottomArgs, config: &ConfigV1) -> OptionResult<ColourScheme> { fn get_network_unit_type(args: &BottomArgs, config: &Config) -> DataUnit {
if let Some(color) = &args.style.color {
// Highest priority is always command line flags...
return ColourScheme::from_str(color);
} else if let Some(colors) = &config.colors {
if !colors.is_empty() {
// 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);
}
}
} 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)
}
fn get_network_unit_type(args: &BottomArgs, config: &ConfigV1) -> DataUnit {
if args.network.network_use_bytes { if args.network.network_use_bytes {
return DataUnit::Byte; return DataUnit::Byte;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -849,7 +826,7 @@ fn get_network_unit_type(args: &BottomArgs, config: &ConfigV1) -> DataUnit {
DataUnit::Bit DataUnit::Bit
} }
fn get_network_scale_type(args: &BottomArgs, config: &ConfigV1) -> AxisScaling { fn get_network_scale_type(args: &BottomArgs, config: &Config) -> AxisScaling {
if args.network.network_use_log { if args.network.network_use_log {
return AxisScaling::Log; return AxisScaling::Log;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -863,7 +840,7 @@ fn get_network_scale_type(args: &BottomArgs, config: &ConfigV1) -> AxisScaling {
AxisScaling::Linear AxisScaling::Linear
} }
fn get_retention(args: &BottomArgs, config: &ConfigV1) -> OptionResult<u64> { fn get_retention(args: &BottomArgs, config: &Config) -> OptionResult<u64> {
const DEFAULT_RETENTION_MS: u64 = 600 * 1000; // Keep 10 minutes of data. const DEFAULT_RETENTION_MS: u64 = 600 * 1000; // Keep 10 minutes of data.
parse_ms_option!( parse_ms_option!(
@ -880,7 +857,7 @@ fn get_retention(args: &BottomArgs, config: &ConfigV1) -> OptionResult<u64> {
} }
fn get_network_legend_position( fn get_network_legend_position(
args: &BottomArgs, config: &ConfigV1, args: &BottomArgs, config: &Config,
) -> OptionResult<Option<LegendPosition>> { ) -> OptionResult<Option<LegendPosition>> {
let result = if let Some(s) = &args.network.network_legend { let result = if let Some(s) = &args.network.network_legend {
match s.to_ascii_lowercase().trim() { match s.to_ascii_lowercase().trim() {
@ -901,7 +878,7 @@ fn get_network_legend_position(
} }
fn get_memory_legend_position( fn get_memory_legend_position(
args: &BottomArgs, config: &ConfigV1, args: &BottomArgs, config: &Config,
) -> OptionResult<Option<LegendPosition>> { ) -> OptionResult<Option<LegendPosition>> {
let result = if let Some(s) = &args.memory.memory_legend { let result = if let Some(s) = &args.memory.memory_legend {
match s.to_ascii_lowercase().trim() { match s.to_ascii_lowercase().trim() {
@ -925,13 +902,12 @@ fn get_memory_legend_position(
mod test { mod test {
use clap::Parser; use clap::Parser;
use super::{get_color_scheme, get_time_interval, ConfigV1}; use super::{get_time_interval, Config};
use crate::{ use crate::{
app::App, app::App,
args::BottomArgs, args::BottomArgs,
canvas::styling::CanvasStyling,
options::{ options::{
config::FlagConfig, get_default_time_value, get_retention, get_update_rate, config::flags::FlagConfig, get_default_time_value, get_retention, get_update_rate,
try_parse_ms, try_parse_ms,
}, },
}; };
@ -957,7 +933,7 @@ mod test {
#[test] #[test]
fn matches_human_times() { fn matches_human_times() {
let config = ConfigV1::default(); let config = Config::default();
{ {
let delta_args = vec!["btm", "--time_delta", "2 min"]; let delta_args = vec!["btm", "--time_delta", "2 min"];
@ -982,7 +958,7 @@ mod test {
#[test] #[test]
fn matches_number_times() { fn matches_number_times() {
let config = ConfigV1::default(); let config = Config::default();
{ {
let delta_args = vec!["btm", "--time_delta", "120000"]; let delta_args = vec!["btm", "--time_delta", "120000"];
@ -1009,7 +985,7 @@ mod test {
fn config_human_times() { fn config_human_times() {
let args = BottomArgs::parse_from(["btm"]); let args = BottomArgs::parse_from(["btm"]);
let mut config = ConfigV1::default(); let mut config = Config::default();
let flags = FlagConfig { let flags = FlagConfig {
time_delta: Some("2 min".to_string().into()), time_delta: Some("2 min".to_string().into()),
default_time_value: Some("300s".to_string().into()), default_time_value: Some("300s".to_string().into()),
@ -1039,7 +1015,7 @@ mod test {
fn config_number_times_as_string() { fn config_number_times_as_string() {
let args = BottomArgs::parse_from(["btm"]); let args = BottomArgs::parse_from(["btm"]);
let mut config = ConfigV1::default(); let mut config = Config::default();
let flags = FlagConfig { let flags = FlagConfig {
time_delta: Some("120000".to_string().into()), time_delta: Some("120000".to_string().into()),
default_time_value: Some("300000".to_string().into()), default_time_value: Some("300000".to_string().into()),
@ -1069,7 +1045,7 @@ mod test {
fn config_number_times_as_num() { fn config_number_times_as_num() {
let args = BottomArgs::parse_from(["btm"]); let args = BottomArgs::parse_from(["btm"]);
let mut config = ConfigV1::default(); let mut config = Config::default();
let flags = FlagConfig { let flags = FlagConfig {
time_delta: Some(120000.into()), time_delta: Some(120000.into()),
default_time_value: Some(300000.into()), default_time_value: Some(300000.into()),
@ -1096,11 +1072,8 @@ mod test {
} }
fn create_app(args: BottomArgs) -> App { fn create_app(args: BottomArgs) -> App {
let config = ConfigV1::default(); let config = Config::default();
let styling = super::init_app(args, config).unwrap().0
CanvasStyling::new(get_color_scheme(&args, &config).unwrap(), &config).unwrap();
super::init_app(args, config, &styling).unwrap().0
} }
// TODO: There's probably a better way to create clap options AND unify together // TODO: There's probably a better way to create clap options AND unify together

View file

@ -542,10 +542,10 @@ pub struct StyleArgs {
], ],
hide_possible_values = true, hide_possible_values = true,
help = indoc! { help = indoc! {
"Use a color scheme, use '--help' for info on the colors. [possible values: default, default-light, gruvbox, gruvbox-light, nord, nord-light]", "Use a built-in color theme, use '--help' for info on the colors. [possible values: default, default-light, gruvbox, gruvbox-light, nord, nord-light]",
}, },
long_help = indoc! { long_help = indoc! {
"Use a pre-defined color scheme. Currently supported values are: "Use a pre-defined color theme. Currently supported themes are:
- default - default
- default-light (default but adjusted for lighter backgrounds) - default-light (default but adjusted for lighter backgrounds)
- gruvbox (a bright theme with 'retro groove' colors) - gruvbox (a bright theme with 'retro groove' colors)
@ -554,7 +554,7 @@ pub struct StyleArgs {
- nord-light (nord but adjusted for lighter backgrounds)" - nord-light (nord but adjusted for lighter backgrounds)"
} }
)] )]
pub color: Option<String>, pub theme: Option<String>,
} }
/// Other arguments. This just handle options that are for help/version /// Other arguments. This just handle options that are for help/version

View file

@ -1,47 +0,0 @@
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
/// Colour configuration.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub struct ColoursConfig {
// TODO: Make these an enum instead.
pub table_header_color: Option<Cow<'static, str>>,
pub all_cpu_color: Option<Cow<'static, str>>,
pub avg_cpu_color: Option<Cow<'static, str>>,
pub cpu_core_colors: Option<Vec<Cow<'static, str>>>,
pub ram_color: Option<Cow<'static, str>>,
#[cfg(not(target_os = "windows"))]
pub cache_color: Option<Cow<'static, str>>,
pub swap_color: Option<Cow<'static, str>>,
pub arc_color: Option<Cow<'static, str>>,
pub gpu_core_colors: Option<Vec<Cow<'static, str>>>,
pub rx_color: Option<Cow<'static, str>>,
pub tx_color: Option<Cow<'static, str>>,
pub rx_total_color: Option<Cow<'static, str>>, // These only affect basic mode.
pub tx_total_color: Option<Cow<'static, str>>, // These only affect basic mode.
pub border_color: Option<Cow<'static, str>>,
pub highlighted_border_color: Option<Cow<'static, str>>,
pub disabled_text_color: Option<Cow<'static, str>>,
pub text_color: Option<Cow<'static, str>>,
pub selected_text_color: Option<Cow<'static, str>>,
pub selected_bg_color: Option<Cow<'static, str>>,
pub widget_title_color: Option<Cow<'static, str>>,
pub graph_color: Option<Cow<'static, str>>,
pub high_battery_color: Option<Cow<'static, str>>,
pub medium_battery_color: Option<Cow<'static, str>>,
pub low_battery_color: Option<Cow<'static, str>>,
}
impl ColoursConfig {
/// Returns `true` if there is a [`ConfigColours`] that is empty or there
/// isn't one at all.
pub fn is_empty(&self) -> bool {
if let Ok(serialized_string) = toml_edit::ser::to_string(self) {
return serialized_string.is_empty();
}
true
}
}

View file

@ -1,19 +1,22 @@
pub mod cpu; pub mod cpu;
pub mod disk; pub mod disk;
pub mod flags;
mod ignore_list; mod ignore_list;
pub mod layout; pub mod layout;
pub mod network; pub mod network;
pub mod process; pub mod process;
pub mod style;
pub mod temperature; pub mod temperature;
use disk::DiskConfig; use disk::DiskConfig;
use flags::FlagConfig;
use network::NetworkConfig; use network::NetworkConfig;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use style::StyleConfig;
use temperature::TempConfig; use temperature::TempConfig;
pub use self::ignore_list::IgnoreList; pub use self::ignore_list::IgnoreList;
use self::{cpu::CpuConfig, layout::Row, process::ProcessesConfig}; use self::{cpu::CpuConfig, layout::Row, process::ProcessesConfig};
use super::ColoursConfig;
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr( #[cfg_attr(
@ -21,9 +24,9 @@ use super::ColoursConfig;
derive(schemars::JsonSchema), derive(schemars::JsonSchema),
schemars(title = "Schema for bottom's configs (nightly)") schemars(title = "Schema for bottom's configs (nightly)")
)] )]
pub struct ConfigV1 { pub struct Config {
pub(crate) flags: Option<FlagConfig>, pub(crate) flags: Option<FlagConfig>,
pub(crate) colors: Option<ColoursConfig>, pub(crate) styles: Option<StyleConfig>,
pub(crate) row: Option<Vec<Row>>, pub(crate) row: Option<Vec<Row>>,
pub(crate) processes: Option<ProcessesConfig>, pub(crate) processes: Option<ProcessesConfig>,
pub(crate) disk: Option<DiskConfig>, pub(crate) disk: Option<DiskConfig>,
@ -51,47 +54,3 @@ impl From<u64> for StringOrNum {
StringOrNum::Num(value) StringOrNum::Num(value)
} }
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct FlagConfig {
pub(crate) hide_avg_cpu: Option<bool>,
pub(crate) dot_marker: Option<bool>,
pub(crate) temperature_type: Option<String>,
pub(crate) rate: Option<StringOrNum>,
pub(crate) cpu_left_legend: Option<bool>,
pub(crate) current_usage: Option<bool>,
pub(crate) unnormalized_cpu: Option<bool>,
pub(crate) group_processes: Option<bool>,
pub(crate) case_sensitive: Option<bool>,
pub(crate) whole_word: Option<bool>,
pub(crate) regex: Option<bool>,
pub(crate) basic: Option<bool>,
pub(crate) default_time_value: Option<StringOrNum>,
pub(crate) time_delta: Option<StringOrNum>,
pub(crate) autohide_time: Option<bool>,
pub(crate) hide_time: Option<bool>,
pub(crate) default_widget_type: Option<String>,
pub(crate) default_widget_count: Option<u64>,
pub(crate) expanded: Option<bool>,
pub(crate) use_old_network_legend: Option<bool>,
pub(crate) hide_table_gap: Option<bool>,
pub(crate) battery: Option<bool>,
pub(crate) disable_click: 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.
pub(crate) color: Option<String>,
pub(crate) process_memory_as_value: Option<bool>,
pub(crate) tree: Option<bool>,
pub(crate) show_table_scroll_position: Option<bool>,
pub(crate) process_command: Option<bool>,
pub(crate) disable_advanced_kill: Option<bool>,
pub(crate) network_use_bytes: Option<bool>,
pub(crate) network_use_log: Option<bool>,
pub(crate) network_use_binary_prefix: Option<bool>,
pub(crate) enable_gpu: Option<bool>,
pub(crate) enable_cache_memory: Option<bool>,
pub(crate) retention: Option<StringOrNum>,
}

View file

@ -0,0 +1,45 @@
use serde::{Deserialize, Serialize};
use super::StringOrNum;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct FlagConfig {
pub(crate) hide_avg_cpu: Option<bool>,
pub(crate) dot_marker: Option<bool>,
pub(crate) temperature_type: Option<String>,
pub(crate) rate: Option<StringOrNum>,
pub(crate) cpu_left_legend: Option<bool>,
pub(crate) current_usage: Option<bool>,
pub(crate) unnormalized_cpu: Option<bool>,
pub(crate) group_processes: Option<bool>,
pub(crate) case_sensitive: Option<bool>,
pub(crate) whole_word: Option<bool>,
pub(crate) regex: Option<bool>,
pub(crate) basic: Option<bool>,
pub(crate) default_time_value: Option<StringOrNum>,
pub(crate) time_delta: Option<StringOrNum>,
pub(crate) autohide_time: Option<bool>,
pub(crate) hide_time: Option<bool>,
pub(crate) default_widget_type: Option<String>,
pub(crate) default_widget_count: Option<u64>,
pub(crate) expanded: Option<bool>,
pub(crate) use_old_network_legend: Option<bool>,
pub(crate) hide_table_gap: Option<bool>,
pub(crate) battery: Option<bool>,
pub(crate) disable_click: Option<bool>,
pub(crate) no_write: Option<bool>,
pub(crate) network_legend: Option<String>,
pub(crate) memory_legend: Option<String>,
pub(crate) process_memory_as_value: Option<bool>,
pub(crate) tree: Option<bool>,
pub(crate) show_table_scroll_position: Option<bool>,
pub(crate) process_command: Option<bool>,
pub(crate) disable_advanced_kill: Option<bool>,
pub(crate) network_use_bytes: Option<bool>,
pub(crate) network_use_log: Option<bool>,
pub(crate) network_use_binary_prefix: Option<bool>,
pub(crate) enable_gpu: Option<bool>,
pub(crate) enable_cache_memory: Option<bool>,
pub(crate) retention: Option<StringOrNum>,
}

View file

@ -242,7 +242,7 @@ mod test {
use super::*; use super::*;
use crate::{ use crate::{
constants::{DEFAULT_LAYOUT, DEFAULT_WIDGET_ID}, constants::{DEFAULT_LAYOUT, DEFAULT_WIDGET_ID},
options::ConfigV1, options::Config,
}; };
const PROC_LAYOUT: &str = r#" const PROC_LAYOUT: &str = r#"
@ -295,7 +295,7 @@ mod test {
#[test] #[test]
/// Tests the default setup. /// Tests the default setup.
fn test_default_movement() { fn test_default_movement() {
let rows = from_str::<ConfigV1>(DEFAULT_LAYOUT).unwrap().row.unwrap(); let rows = from_str::<Config>(DEFAULT_LAYOUT).unwrap().row.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false); let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
// Simple tests for the top CPU widget // Simple tests for the top CPU widget
@ -369,7 +369,7 @@ mod test {
fn test_default_battery_movement() { fn test_default_battery_movement() {
use crate::constants::DEFAULT_BATTERY_LAYOUT; use crate::constants::DEFAULT_BATTERY_LAYOUT;
let rows = from_str::<ConfigV1>(DEFAULT_BATTERY_LAYOUT) let rows = from_str::<Config>(DEFAULT_BATTERY_LAYOUT)
.unwrap() .unwrap()
.row .row
.unwrap(); .unwrap();
@ -415,7 +415,7 @@ mod test {
#[test] #[test]
/// Tests using cpu_left_legend. /// Tests using cpu_left_legend.
fn test_cpu_left_legend() { fn test_cpu_left_legend() {
let rows = from_str::<ConfigV1>(DEFAULT_LAYOUT).unwrap().row.unwrap(); let rows = from_str::<Config>(DEFAULT_LAYOUT).unwrap().row.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true); let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true);
// Legend // Legend
@ -475,7 +475,7 @@ mod test {
type="proc" type="proc"
"#; "#;
let rows = from_str::<ConfigV1>(proc_layout).unwrap().row.unwrap(); let rows = from_str::<Config>(proc_layout).unwrap().row.unwrap();
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs* let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0; let mut total_height_ratio = 0;
let mut default_widget_count = 1; let mut default_widget_count = 1;
@ -508,7 +508,7 @@ mod test {
#[test] #[test]
/// Tests default widget by setting type and count. /// Tests default widget by setting type and count.
fn test_default_widget_by_option() { fn test_default_widget_by_option() {
let rows = from_str::<ConfigV1>(PROC_LAYOUT).unwrap().row.unwrap(); let rows = from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs* let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0; let mut total_height_ratio = 0;
let mut default_widget_count = 3; let mut default_widget_count = 3;
@ -540,7 +540,7 @@ mod test {
#[test] #[test]
fn test_proc_custom_layout() { fn test_proc_custom_layout() {
let rows = from_str::<ConfigV1>(PROC_LAYOUT).unwrap().row.unwrap(); let rows = from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false); let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
// First proc widget // First proc widget

260
src/options/config/style.rs Normal file
View file

@ -0,0 +1,260 @@
//! Config options around styling.
mod battery;
mod cpu;
mod graphs;
mod memory;
mod network;
mod tables;
mod themes;
mod utils;
mod widgets;
use std::borrow::Cow;
use battery::BatteryStyle;
use cpu::CpuStyle;
use graphs::GraphStyle;
use memory::MemoryStyle;
use network::NetworkStyle;
use serde::{Deserialize, Serialize};
use tables::TableStyle;
use tui::style::Style;
use utils::{opt, set_colour, set_colour_list, set_style};
use widgets::WidgetStyle;
use crate::options::{args::BottomArgs, OptionError, OptionResult};
use super::Config;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct ColorStr(Cow<'static, str>);
/// A style for text.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct TextStyleConfig {
/// A built-in ANSI colour, RGB hex, or RGB colour code.
#[serde(alias = "colour")]
pub(crate) color: Option<ColorStr>,
/// A built-in ANSI colour, RGB hex, or RGB colour code.
#[serde(alias = "bg_colour")]
pub(crate) bg_color: Option<ColorStr>,
/// Whether to make this text bolded or not. If not set,
/// will default to built-in defaults.
pub(crate) bold: Option<bool>,
}
/// Style-related configs.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct StyleConfig {
/// A built-in theme.
///
/// If this is and a custom colour are both set, in the config file,
/// the custom colour scheme will be prioritized first. If a theme
/// is set in the command-line args, however, it will always be
/// prioritized first.
pub(crate) theme: Option<Cow<'static, str>>,
/// Styling for the CPU widget.
pub(crate) cpu: Option<CpuStyle>,
/// Styling for the memory widget.
pub(crate) memory: Option<MemoryStyle>,
/// Styling for the network widget.
pub(crate) network: Option<NetworkStyle>,
/// Styling for the battery widget.
pub(crate) battery: Option<BatteryStyle>,
/// Styling for table widgets.
pub(crate) tables: Option<TableStyle>,
/// Styling for graph widgets.
pub(crate) graphs: Option<GraphStyle>,
/// Styling for general widgets.
pub(crate) widgets: Option<WidgetStyle>,
}
/// The actual internal representation of the configured colours,
/// as a "palette".
#[derive(Debug)]
pub struct ColourPalette {
pub selected_text_style: Style,
pub table_header_style: Style,
pub ram_style: Style,
#[cfg(not(target_os = "windows"))]
pub cache_style: Style,
pub swap_style: Style,
pub arc_style: Style,
pub gpu_colours: Vec<Style>,
pub rx_style: Style,
pub tx_style: Style,
pub total_rx_style: Style,
pub total_tx_style: Style,
pub all_cpu_colour: Style,
pub avg_cpu_colour: Style,
pub cpu_colour_styles: Vec<Style>,
pub border_style: Style,
pub highlighted_border_style: Style,
pub text_style: Style,
pub widget_title_style: Style,
pub graph_style: Style,
pub graph_legend_style: Style,
pub high_battery: Style,
pub medium_battery: Style,
pub low_battery: Style,
pub invalid_query_style: Style,
pub disabled_text_style: Style,
}
impl Default for ColourPalette {
fn default() -> Self {
Self::default_palette()
}
}
impl ColourPalette {
pub fn new(args: &BottomArgs, config: &Config) -> anyhow::Result<Self> {
let mut palette = match &args.style.theme {
Some(theme) => Self::from_theme(theme)?,
None => match config.styles.as_ref().and_then(|s| s.theme.as_ref()) {
Some(theme) => Self::from_theme(theme)?,
None => Self::default(),
},
};
// Apply theme from config on top.
if let Some(style) = &config.styles {
palette.set_colours_from_palette(style)?;
}
Ok(palette)
}
fn from_theme(theme: &str) -> anyhow::Result<Self> {
let lower_case = theme.to_lowercase();
match lower_case.as_str() {
"default" => Ok(Self::default_palette()),
"default-light" => Ok(Self::default_light_mode()),
"gruvbox" => Ok(Self::gruvbox_palette()),
"gruvbox-light" => Ok(Self::gruvbox_light_palette()),
"nord" => Ok(Self::nord_palette()),
"nord-light" => Ok(Self::nord_light_palette()),
_ => Err(
OptionError::other(format!("'{theme}' is an invalid built-in color scheme."))
.into(),
),
}
}
fn set_colours_from_palette(&mut self, config: &StyleConfig) -> OptionResult<()> {
// CPU
set_colour!(self.avg_cpu_colour, config.cpu, avg_entry_color);
set_colour!(self.all_cpu_colour, config.cpu, all_entry_color);
set_colour_list!(self.cpu_colour_styles, config.cpu, cpu_core_colors);
// Memory
set_colour!(self.ram_style, config.memory, ram);
set_colour!(self.swap_style, config.memory, swap);
#[cfg(not(target_os = "windows"))]
set_colour!(self.cache_style, config.memory, cache);
#[cfg(feature = "zfs")]
set_colour!(self.arc_style, config.memory, arc);
#[cfg(feature = "gpu")]
set_colour_list!(self.gpu_colours, config.memory, gpus);
// Network
set_colour!(self.rx_style, config.network, rx);
set_colour!(self.tx_style, config.network, tx);
set_colour!(self.total_rx_style, config.network, rx_total);
set_colour!(self.total_tx_style, config.network, tx_total);
// Battery
set_colour!(self.high_battery, config.battery, high_battery);
set_colour!(self.medium_battery, config.battery, medium_battery);
set_colour!(self.low_battery, config.battery, low_battery);
// Tables
set_style!(self.table_header_style, config.tables, headers);
// Widget graphs
set_colour!(self.graph_style, config.graphs, graph_color);
set_style!(self.graph_legend_style, config.graphs, legend_text);
// General widget text.
set_style!(self.widget_title_style, config.widgets, widget_title);
set_style!(self.text_style, config.widgets, text);
set_style!(self.selected_text_style, config.widgets, selected_text);
set_style!(self.disabled_text_style, config.widgets, disabled_text);
// Widget borders
set_colour!(self.border_style, config.widgets, border);
set_colour!(
self.highlighted_border_style,
config.widgets,
selected_border
);
Ok(())
}
}
#[cfg(test)]
mod test {
use tui::style::{Color, Style};
use super::ColourPalette;
use crate::options::config::style::utils::str_to_colour;
#[test]
fn default_selected_colour_works() {
let mut colours = ColourPalette::default();
println!("colours: {colours:?}");
let original_selected_text_colour = ColourPalette::default_palette()
.selected_text_style
.fg
.unwrap();
let original_selected_bg_colour = ColourPalette::default_palette()
.selected_text_style
.bg
.unwrap();
assert_eq!(
colours.selected_text_style,
Style::default()
.fg(original_selected_text_colour)
.bg(original_selected_bg_colour),
);
colours.selected_text_style = colours
.selected_text_style
.fg(str_to_colour("magenta").unwrap())
.bg(str_to_colour("red").unwrap());
assert_eq!(
colours.selected_text_style,
Style::default().fg(Color::Magenta).bg(Color::Red),
);
}
#[test]
fn built_in_colour_schemes_work() {
ColourPalette::from_theme("default").unwrap();
ColourPalette::from_theme("default-light").unwrap();
ColourPalette::from_theme("gruvbox").unwrap();
ColourPalette::from_theme("gruvbox-light").unwrap();
ColourPalette::from_theme("nord").unwrap();
ColourPalette::from_theme("nord-light").unwrap();
}
}

View file

@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
use super::ColorStr;
/// Styling specific to the battery widget.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct BatteryStyle {
pub(crate) high_battery: Option<ColorStr>,
pub(crate) medium_battery: Option<ColorStr>,
pub(crate) low_battery: Option<ColorStr>,
}

View file

@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
use super::ColorStr;
/// Styling specific to the CPU widget.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct CpuStyle {
// TODO: Should I change the name of these?
#[serde(alias = "all_entry_colour")]
pub(crate) all_entry_color: Option<ColorStr>,
#[serde(alias = "avg_entry_colour")]
pub(crate) avg_entry_color: Option<ColorStr>,
#[serde(alias = "cpu_core_colours")]
pub(crate) cpu_core_colors: Option<Vec<ColorStr>>,
}

View file

@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
use super::{ColorStr, TextStyleConfig};
/// General styling for graph widgets.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct GraphStyle {
#[serde(alias = "graph_colour")]
pub(crate) graph_color: Option<ColorStr>,
pub(crate) legend_text: Option<TextStyleConfig>,
}

View file

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
use super::ColorStr;
/// Styling specific to the memory widget.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct MemoryStyle {
pub(crate) ram: Option<ColorStr>,
#[cfg(not(target_os = "windows"))]
pub(crate) cache: Option<ColorStr>,
pub(crate) swap: Option<ColorStr>,
pub(crate) arc: Option<ColorStr>,
pub(crate) gpus: Option<Vec<ColorStr>>,
}

View file

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
use super::ColorStr;
/// Styling specific to the network widget.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct NetworkStyle {
pub(crate) rx: Option<ColorStr>,
pub(crate) tx: Option<ColorStr>,
/// Set the colour of the "rx total" text. This only affects
/// basic mode.
pub(crate) rx_total: Option<ColorStr>,
/// Set the colour of the "tx total" text. This only affects
/// basic mode.
pub(crate) tx_total: Option<ColorStr>,
}

View file

@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
use super::TextStyleConfig;
/// General styling for table widgets.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct TableStyle {
pub(crate) headers: Option<TextStyleConfig>,
}

View file

@ -0,0 +1,20 @@
//! A set of pre-defined themes.
pub(super) mod default;
pub(super) mod gruvbox;
pub(super) mod nord;
macro_rules! color {
($value:expr) => {
tui::style::Style::new().fg($value)
};
}
macro_rules! hex {
($value:literal) => {
tui::style::Style::new()
.fg(crate::options::config::style::utils::convert_hex_to_color($value.into()).unwrap())
};
}
pub(super) use {color, hex};

View file

@ -0,0 +1,110 @@
use tui::style::{Color, Style};
use crate::options::config::style::ColourPalette;
use super::color;
impl ColourPalette {
pub(crate) fn default_palette() -> Self {
const FIRST_COLOUR: Color = Color::LightMagenta;
const SECOND_COLOUR: Color = Color::LightYellow;
const THIRD_COLOUR: Color = Color::LightCyan;
const FOURTH_COLOUR: Color = Color::LightGreen;
#[cfg(not(target_os = "windows"))]
const FIFTH_COLOUR: Color = Color::LightRed;
const HIGHLIGHT_COLOUR: Color = Color::LightBlue;
const AVG_COLOUR: Color = Color::Red;
const ALL_COLOUR: Color = Color::Green;
const DEFAULT_SELECTED_TEXT_STYLE: Style = color!(Color::Black).bg(HIGHLIGHT_COLOUR);
const TEXT_COLOUR: Color = Color::Gray;
Self {
selected_text_style: DEFAULT_SELECTED_TEXT_STYLE,
table_header_style: color!(HIGHLIGHT_COLOUR),
ram_style: color!(FIRST_COLOUR),
#[cfg(not(target_os = "windows"))]
cache_style: color!(FIFTH_COLOUR),
swap_style: color!(SECOND_COLOUR),
arc_style: color!(THIRD_COLOUR),
gpu_colours: vec![
color!(FOURTH_COLOUR),
color!(Color::LightBlue),
color!(Color::LightRed),
color!(Color::Cyan),
color!(Color::Green),
color!(Color::Blue),
color!(Color::Red),
],
rx_style: color!(FIRST_COLOUR),
tx_style: color!(SECOND_COLOUR),
total_rx_style: color!(THIRD_COLOUR),
total_tx_style: color!(FOURTH_COLOUR),
all_cpu_colour: color!(ALL_COLOUR),
avg_cpu_colour: color!(AVG_COLOUR),
cpu_colour_styles: vec![
color!(Color::LightMagenta),
color!(Color::LightYellow),
color!(Color::LightCyan),
color!(Color::LightGreen),
color!(Color::LightBlue),
color!(Color::Cyan),
color!(Color::Green),
color!(Color::Blue),
],
border_style: color!(TEXT_COLOUR),
highlighted_border_style: color!(HIGHLIGHT_COLOUR),
text_style: color!(TEXT_COLOUR),
widget_title_style: color!(TEXT_COLOUR),
graph_style: color!(TEXT_COLOUR),
graph_legend_style: color!(TEXT_COLOUR),
high_battery: color!(Color::Green),
medium_battery: color!(Color::Yellow),
low_battery: color!(Color::Red),
invalid_query_style: color!(Color::Red),
disabled_text_style: color!(Color::DarkGray),
}
}
pub fn default_light_mode() -> Self {
Self {
selected_text_style: color!(Color::White),
table_header_style: color!(Color::Black),
ram_style: color!(Color::Blue),
#[cfg(not(target_os = "windows"))]
cache_style: color!(Color::LightRed),
swap_style: color!(Color::Red),
arc_style: color!(Color::LightBlue),
gpu_colours: vec![
color!(Color::LightGreen),
color!(Color::LightCyan),
color!(Color::LightRed),
color!(Color::Cyan),
color!(Color::Green),
color!(Color::Blue),
color!(Color::Red),
],
rx_style: color!(Color::Blue),
tx_style: color!(Color::Red),
total_rx_style: color!(Color::LightBlue),
total_tx_style: color!(Color::LightRed),
cpu_colour_styles: vec![
color!(Color::LightMagenta),
color!(Color::LightBlue),
color!(Color::LightRed),
color!(Color::Cyan),
color!(Color::Green),
color!(Color::Blue),
color!(Color::Red),
],
border_style: color!(Color::Black),
text_style: color!(Color::Black),
widget_title_style: color!(Color::Black),
graph_style: color!(Color::Black),
graph_legend_style: color!(Color::Black),
disabled_text_style: color!(Color::Gray),
..Self::default_palette()
}
}
}

View file

@ -0,0 +1,127 @@
use tui::style::Color;
use crate::options::config::style::{utils::convert_hex_to_color, ColourPalette};
use super::{color, hex};
impl ColourPalette {
pub(crate) fn gruvbox_palette() -> Self {
Self {
selected_text_style: hex!("#1d2021").bg(convert_hex_to_color("#ebdbb2").unwrap()),
table_header_style: hex!("#83a598"),
ram_style: hex!("#8ec07c"),
#[cfg(not(target_os = "windows"))]
cache_style: hex!("#b16286"),
swap_style: hex!("#fabd2f"),
arc_style: hex!("#689d6a"),
gpu_colours: vec![
hex!("#d79921"),
hex!("#458588"),
hex!("#b16286"),
hex!("#fe8019"),
hex!("#b8bb26"),
hex!("#cc241d"),
hex!("#98971a"),
],
rx_style: hex!("#8ec07c"),
tx_style: hex!("#fabd2f"),
total_rx_style: hex!("#689d6a"),
total_tx_style: hex!("#d79921"),
all_cpu_colour: hex!("#8ec07c"),
avg_cpu_colour: hex!("#fb4934"),
cpu_colour_styles: vec![
hex!("#cc241d"),
hex!("#98971a"),
hex!("#d79921"),
hex!("#458588"),
hex!("#b16286"),
hex!("#689d6a"),
hex!("#fe8019"),
hex!("#b8bb26"),
hex!("#fabd2f"),
hex!("#83a598"),
hex!("#d3869b"),
hex!("#d65d0e"),
hex!("#9d0006"),
hex!("#79740e"),
hex!("#b57614"),
hex!("#076678"),
hex!("#8f3f71"),
hex!("#427b58"),
hex!("#d65d03"),
hex!("#af3a03"),
],
border_style: hex!("#ebdbb2"),
highlighted_border_style: hex!("#fe8019"),
text_style: hex!("#ebdbb2"),
widget_title_style: hex!("#ebdbb2"),
graph_style: hex!("#ebdbb2"),
graph_legend_style: hex!("#ebdbb2"),
high_battery: hex!("#98971a"),
medium_battery: hex!("#fabd2f"),
low_battery: hex!("#fb4934"),
invalid_query_style: color!(Color::Red),
disabled_text_style: hex!("#665c54"),
}
}
pub(crate) fn gruvbox_light_palette() -> Self {
Self {
selected_text_style: hex!("#ebdbb2").bg(convert_hex_to_color("#3c3836").unwrap()),
table_header_style: hex!("#076678"),
ram_style: hex!("#427b58"),
#[cfg(not(target_os = "windows"))]
cache_style: hex!("#d79921"),
swap_style: hex!("#cc241d"),
arc_style: hex!("#689d6a"),
gpu_colours: vec![
hex!("#9d0006"),
hex!("#98971a"),
hex!("#d79921"),
hex!("#458588"),
hex!("#b16286"),
hex!("#fe8019"),
hex!("#b8bb26"),
],
rx_style: hex!("#427b58"),
tx_style: hex!("#cc241d"),
total_rx_style: hex!("#689d6a"),
total_tx_style: hex!("#d79921"),
all_cpu_colour: hex!("#8ec07c"),
avg_cpu_colour: hex!("#fb4934"),
cpu_colour_styles: vec![
hex!("#cc241d"),
hex!("#98971a"),
hex!("#d79921"),
hex!("#458588"),
hex!("#b16286"),
hex!("#689d6a"),
hex!("#fe8019"),
hex!("#b8bb26"),
hex!("#fabd2f"),
hex!("#83a598"),
hex!("#d3869b"),
hex!("#d65d0e"),
hex!("#9d0006"),
hex!("#79740e"),
hex!("#b57614"),
hex!("#076678"),
hex!("#8f3f71"),
hex!("#427b58"),
hex!("#d65d03"),
hex!("#af3a03"),
],
border_style: hex!("#ebdbb2"),
highlighted_border_style: hex!("#fe8019"),
text_style: hex!("#ebdbb2"),
widget_title_style: hex!("#ebdbb2"),
graph_style: hex!("#ebdbb2"),
graph_legend_style: hex!("#ebdbb2"),
high_battery: hex!("#98971a"),
medium_battery: hex!("#fabd2f"),
low_battery: hex!("#fb4934"),
invalid_query_style: color!(Color::Red),
disabled_text_style: hex!("#665c54"),
}
}
}

View file

@ -0,0 +1,103 @@
use tui::style::Color;
use crate::options::config::style::{utils::convert_hex_to_color, ColourPalette};
use super::{color, hex};
impl ColourPalette {
pub(crate) fn nord_palette() -> Self {
Self {
selected_text_style: hex!("#2e3440").bg(convert_hex_to_color("#88c0d0").unwrap()),
table_header_style: hex!("#81a1c1"),
ram_style: hex!("#88c0d0"),
#[cfg(not(target_os = "windows"))]
cache_style: hex!("#d8dee9"),
swap_style: hex!("#d08770"),
arc_style: hex!("#5e81ac"),
gpu_colours: vec![
hex!("#8fbcbb"),
hex!("#81a1c1"),
hex!("#d8dee9"),
hex!("#b48ead"),
hex!("#a3be8c"),
hex!("#ebcb8b"),
hex!("#bf616a"),
],
rx_style: hex!("#88c0d0"),
tx_style: hex!("#d08770"),
total_rx_style: hex!("#5e81ac"),
total_tx_style: hex!("#8fbcbb"),
all_cpu_colour: hex!("#88c0d0"),
avg_cpu_colour: hex!("#8fbcbb"),
cpu_colour_styles: vec![
hex!("#5e81ac"),
hex!("#81a1c1"),
hex!("#d8dee9"),
hex!("#b48ead"),
hex!("#a3be8c"),
hex!("#ebcb8b"),
hex!("#d08770"),
hex!("#bf616a"),
],
border_style: hex!("#88c0d0"),
highlighted_border_style: hex!("#5e81ac"),
text_style: hex!("#e5e9f0"),
widget_title_style: hex!("#e5e9f0"),
graph_style: hex!("#e5e9f0"),
graph_legend_style: hex!("#e5e9f0"),
high_battery: hex!("#a3be8c"),
medium_battery: hex!("#ebcb8b"),
low_battery: hex!("#bf616a"),
invalid_query_style: color!(Color::Red),
disabled_text_style: hex!("#4c566a"),
}
}
pub(crate) fn nord_light_palette() -> Self {
Self {
selected_text_style: hex!("#f5f5f5").bg(convert_hex_to_color("#5e81ac").unwrap()),
table_header_style: hex!("#5e81ac"),
ram_style: hex!("#81a1c1"),
#[cfg(not(target_os = "windows"))]
cache_style: hex!("#4c566a"),
swap_style: hex!("#d08770"),
arc_style: hex!("#5e81ac"),
gpu_colours: vec![
hex!("#8fbcbb"),
hex!("#88c0d0"),
hex!("#4c566a"),
hex!("#b48ead"),
hex!("#a3be8c"),
hex!("#ebcb8b"),
hex!("#bf616a"),
],
rx_style: hex!("#81a1c1"),
tx_style: hex!("#d08770"),
total_rx_style: hex!("#5e81ac"),
total_tx_style: hex!("#8fbcbb"),
all_cpu_colour: hex!("#81a1c1"),
avg_cpu_colour: hex!("#8fbcbb"),
cpu_colour_styles: vec![
hex!("#5e81ac"),
hex!("#88c0d0"),
hex!("#4c566a"),
hex!("#b48ead"),
hex!("#a3be8c"),
hex!("#ebcb8b"),
hex!("#d08770"),
hex!("#bf616a"),
],
border_style: hex!("#2e3440"),
highlighted_border_style: hex!("#5e81ac"),
text_style: hex!("#2e3440"),
widget_title_style: hex!("#2e3440"),
graph_style: hex!("#2e3440"),
graph_legend_style: hex!("#2e3440"),
high_battery: hex!("#a3be8c"),
medium_battery: hex!("#ebcb8b"),
low_battery: hex!("#bf616a"),
invalid_query_style: color!(Color::Red),
disabled_text_style: hex!("#d8dee9"),
}
}
}

View file

@ -1,20 +1,9 @@
use concat_string::concat_string; use concat_string::concat_string;
use itertools::Itertools;
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
pub const FIRST_COLOUR: Color = Color::LightMagenta;
pub const SECOND_COLOUR: Color = Color::LightYellow;
pub const THIRD_COLOUR: Color = Color::LightCyan;
pub const FOURTH_COLOUR: Color = Color::LightGreen;
#[cfg(not(target_os = "windows"))]
pub const FIFTH_COLOUR: Color = Color::LightRed;
pub const HIGHLIGHT_COLOUR: Color = Color::LightBlue;
pub const AVG_COLOUR: Color = Color::Red;
pub const ALL_COLOUR: Color = Color::Green;
/// Convert a hex string to a colour. /// Convert a hex string to a colour.
fn convert_hex_to_color(hex: &str) -> Result<Color, String> { pub(super) fn convert_hex_to_color(hex: &str) -> Result<Color, String> {
fn hex_component_to_int(hex: &str, first: &str, second: &str) -> Result<u8, String> { fn hex_component_to_int(hex: &str, first: &str, second: &str) -> Result<u8, String> {
u8::from_str_radix(&concat_string!(first, second), 16) u8::from_str_radix(&concat_string!(first, second), 16)
.map_err(|_| format!("'{hex}' is an invalid hex color, could not decode.")) .map_err(|_| format!("'{hex}' is an invalid hex color, could not decode."))
@ -30,7 +19,7 @@ fn convert_hex_to_color(hex: &str) -> Result<Color, String> {
return Err(invalid_hex_format(hex)); return Err(invalid_hex_format(hex));
} }
let components = hex.graphemes(true).collect_vec(); let components: Vec<&str> = hex.graphemes(true).collect();
if components.len() == 7 { if components.len() == 7 {
// A 6-long hex. // A 6-long hex.
let r = hex_component_to_int(hex, components[1], components[2])?; let r = hex_component_to_int(hex, components[1], components[2])?;
@ -50,10 +39,6 @@ fn convert_hex_to_color(hex: &str) -> Result<Color, String> {
} }
} }
pub fn str_to_fg(input_val: &str) -> Result<Style, String> {
Ok(Style::default().fg(str_to_colour(input_val)?))
}
pub fn str_to_colour(input_val: &str) -> Result<Color, String> { pub fn str_to_colour(input_val: &str) -> Result<Color, String> {
if input_val.len() > 1 { if input_val.len() > 1 {
if input_val.starts_with('#') { if input_val.starts_with('#') {
@ -68,6 +53,10 @@ pub fn str_to_colour(input_val: &str) -> Result<Color, String> {
} }
} }
pub(super) fn str_to_fg(input_val: &str) -> Result<Style, String> {
Ok(Style::default().fg(str_to_colour(input_val)?))
}
fn convert_rgb_to_color(rgb_str: &str) -> Result<Color, String> { fn convert_rgb_to_color(rgb_str: &str) -> Result<Color, String> {
let rgb_list = rgb_str.split(',').collect::<Vec<&str>>(); let rgb_list = rgb_str.split(',').collect::<Vec<&str>>();
if rgb_list.len() != 3 { if rgb_list.len() != 3 {
@ -118,7 +107,7 @@ fn convert_name_to_colour(color_name: &str) -> Result<Color, String> {
_ => Err(format!( _ => Err(format!(
"'{color_name}' is an invalid named color. "'{color_name}' is an invalid named color.
The following are supported strings: The following are supported named colors:
+--------+-------------+---------------------+ +--------+-------------+---------------------+
| Reset | Magenta | Light Yellow | | Reset | Magenta | Light Yellow |
+--------+-------------+---------------------+ +--------+-------------+---------------------+
@ -131,13 +120,85 @@ The following are supported strings:
| Yellow | Light Red | White | | Yellow | Light Red | White |
+--------+-------------+---------------------+ +--------+-------------+---------------------+
| Blue | Light Green | | | Blue | Light Green | |
+--------+-------------+---------------------+\n" +--------+-------------+---------------------+
Alternatively, hex colors or RGB color codes are valid.\n"
)), )),
} }
} }
macro_rules! opt {
($($e: tt)+) => {
(|| { $($e)+ })()
}
}
macro_rules! set_style {
($palette_field:expr, $config_location:expr, $field:tt) => {
if let Some(style) = &(opt!($config_location.as_ref()?.$field.as_ref())) {
if let Some(colour) = &style.color {
$palette_field = crate::options::config::style::utils::str_to_fg(&colour.0)
.map_err(|err| match stringify!($config_location).split_once(".") {
Some((_, loc)) => OptionError::config(format!(
"Please update 'styles.{loc}.{}' in your config file. {err}",
stringify!($field)
)),
None => OptionError::config(format!(
"Please update 'styles.{}' in your config file. {err}",
stringify!($field)
)),
})?;
}
}
};
}
macro_rules! set_colour {
($palette_field:expr, $config_location:expr, $field:tt) => {
if let Some(colour) = &(opt!($config_location.as_ref()?.$field.as_ref())) {
$palette_field =
crate::options::config::style::utils::str_to_fg(&colour.0).map_err(|err| {
match stringify!($config_location).split_once(".") {
Some((_, loc)) => OptionError::config(format!(
"Please update 'styles.{loc}.{}' in your config file. {err}",
stringify!($field)
)),
None => OptionError::config(format!(
"Please update 'styles.{}' in your config file. {err}",
stringify!($field)
)),
}
})?;
}
};
}
macro_rules! set_colour_list {
($palette_field:expr, $config_location:expr, $field:tt) => {
if let Some(colour_list) = &(opt!($config_location.as_ref()?.$field.as_ref())) {
$palette_field = colour_list
.iter()
.map(|s| crate::options::config::style::utils::str_to_fg(&s.0))
.collect::<Result<Vec<Style>, String>>()
.map_err(|err| match stringify!($config_location).split_once(".") {
Some((_, loc)) => OptionError::config(format!(
"Please update 'styles.{loc}.{}' in your config file. {err}",
stringify!($field)
)),
None => OptionError::config(format!(
"Please update 'styles.{}' in your config file. {err}",
stringify!($field)
)),
})?;
}
};
}
pub(super) use {opt, set_colour, set_colour_list, set_style};
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]

View file

@ -0,0 +1,16 @@
use serde::{Deserialize, Serialize};
use super::{ColorStr, TextStyleConfig};
/// General styling for generic widgets.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
pub(crate) struct WidgetStyle {
pub(crate) border: Option<ColorStr>,
pub(crate) selected_border: Option<ColorStr>,
pub(crate) widget_title: Option<TextStyleConfig>,
pub(crate) text: Option<TextStyleConfig>,
pub(crate) selected_text: Option<TextStyleConfig>,
pub(crate) disabled_text: Option<TextStyleConfig>,
}

View file

@ -10,12 +10,11 @@ use crate::{
Column, ColumnHeader, DataTable, DataTableColumn, DataTableProps, DataTableStyling, Column, ColumnHeader, DataTable, DataTableColumn, DataTableProps, DataTableStyling,
DataToCell, DataToCell,
}, },
styling::CanvasStyling,
Painter, Painter,
}, },
data_collection::cpu::CpuDataType, data_collection::cpu::CpuDataType,
data_conversion::CpuWidgetData, data_conversion::CpuWidgetData,
options::config::cpu::CpuDefault, options::config::{cpu::CpuDefault, style::ColourPalette},
}; };
#[derive(Default)] #[derive(Default)]
@ -26,16 +25,16 @@ pub struct CpuWidgetStyling {
} }
impl CpuWidgetStyling { impl CpuWidgetStyling {
fn from_colours(colours: &CanvasStyling) -> Self { fn from_colours(palette: &ColourPalette) -> Self {
let entries = if colours.cpu_colour_styles.is_empty() { let entries = if palette.cpu_colour_styles.is_empty() {
vec![Style::default()] vec![Style::default()]
} else { } else {
colours.cpu_colour_styles.clone() palette.cpu_colour_styles.clone()
}; };
Self { Self {
all: colours.all_colour_style, all: palette.all_cpu_colour,
avg: colours.avg_colour_style, avg: palette.avg_cpu_colour,
entries, entries,
} }
} }
@ -130,12 +129,12 @@ impl DataToCell<CpuWidgetColumn> for CpuWidgetTableData {
#[inline(always)] #[inline(always)]
fn style_row<'a>(&self, row: Row<'a>, painter: &Painter) -> Row<'a> { fn style_row<'a>(&self, row: Row<'a>, painter: &Painter) -> Row<'a> {
let style = match self { let style = match self {
CpuWidgetTableData::All => painter.colours.all_colour_style, CpuWidgetTableData::All => painter.colours.all_cpu_colour,
CpuWidgetTableData::Entry { CpuWidgetTableData::Entry {
data_type, data_type,
last_entry: _, last_entry: _,
} => match data_type { } => match data_type {
CpuDataType::Avg => painter.colours.avg_colour_style, CpuDataType::Avg => painter.colours.avg_cpu_colour,
CpuDataType::Cpu(index) => { CpuDataType::Cpu(index) => {
painter.colours.cpu_colour_styles painter.colours.cpu_colour_styles
[index % painter.colours.cpu_colour_styles.len()] [index % painter.colours.cpu_colour_styles.len()]
@ -168,7 +167,7 @@ pub struct CpuWidgetState {
impl CpuWidgetState { impl CpuWidgetState {
pub fn new( pub fn new(
config: &AppConfigFields, default_selection: CpuDefault, current_display_time: u64, config: &AppConfigFields, default_selection: CpuDefault, current_display_time: u64,
autohide_timer: Option<Instant>, colours: &CanvasStyling, autohide_timer: Option<Instant>, colours: &ColourPalette,
) -> Self { ) -> Self {
const COLUMNS: [Column<CpuWidgetColumn>; 2] = [ const COLUMNS: [Column<CpuWidgetColumn>; 2] = [
Column::soft(CpuWidgetColumn::CPU, Some(0.5)), Column::soft(CpuWidgetColumn::CPU, Some(0.5)),
@ -184,7 +183,7 @@ impl CpuWidgetState {
show_current_entry_when_unfocused: true, show_current_entry_when_unfocused: true,
}; };
let styling = DataTableStyling::from_colours(colours); let styling = DataTableStyling::from_palette(colours);
let mut table = DataTable::new(COLUMNS, props, styling); let mut table = DataTable::new(COLUMNS, props, styling);
match default_selection { match default_selection {
CpuDefault::All => {} CpuDefault::All => {}

View file

@ -2,13 +2,11 @@ use std::{borrow::Cow, cmp::max, num::NonZeroU16};
use crate::{ use crate::{
app::AppConfigFields, app::AppConfigFields,
canvas::{ canvas::components::data_table::{
components::data_table::{ ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortColumn,
ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortDataTable, SortDataTableProps, SortOrder, SortsRow,
SortColumn, SortDataTable, SortDataTableProps, SortOrder, SortsRow,
},
styling::CanvasStyling,
}, },
options::config::style::ColourPalette,
utils::{data_prefixes::get_decimal_bytes, general::sort_partial_fn}, utils::{data_prefixes::get_decimal_bytes, general::sort_partial_fn},
}; };
@ -206,7 +204,7 @@ impl SortsRow for DiskWidgetColumn {
} }
impl DiskTableWidget { impl DiskTableWidget {
pub fn new(config: &AppConfigFields, colours: &CanvasStyling) -> Self { pub fn new(config: &AppConfigFields, palette: &ColourPalette) -> Self {
let columns = [ let columns = [
SortColumn::soft(DiskWidgetColumn::Disk, Some(0.2)), SortColumn::soft(DiskWidgetColumn::Disk, Some(0.2)),
SortColumn::soft(DiskWidgetColumn::Mount, Some(0.2)), SortColumn::soft(DiskWidgetColumn::Mount, Some(0.2)),
@ -231,7 +229,7 @@ impl DiskTableWidget {
order: SortOrder::Ascending, order: SortOrder::Ascending,
}; };
let styling = DataTableStyling::from_colours(colours); let styling = DataTableStyling::from_palette(palette);
Self { Self {
table: SortDataTable::new_sortable(columns, props, styling), table: SortDataTable::new_sortable(columns, props, styling),

View file

@ -17,14 +17,12 @@ use crate::{
query::*, query::*,
AppConfigFields, AppSearchState, AppConfigFields, AppSearchState,
}, },
canvas::{ canvas::components::data_table::{
components::data_table::{
Column, ColumnHeader, ColumnWidthBounds, DataTable, DataTableColumn, DataTableProps, Column, ColumnHeader, ColumnWidthBounds, DataTable, DataTableColumn, DataTableProps,
DataTableStyling, SortColumn, SortDataTable, SortDataTableProps, SortOrder, SortsRow, DataTableStyling, SortColumn, SortDataTable, SortDataTableProps, SortOrder, SortsRow,
}, },
styling::CanvasStyling,
},
data_collection::processes::{Pid, ProcessHarvest}, data_collection::processes::{Pid, ProcessHarvest},
options::config::style::ColourPalette,
}; };
/// ProcessSearchState only deals with process' search's current settings and /// ProcessSearchState only deals with process' search's current settings and
@ -161,7 +159,7 @@ pub struct ProcWidgetState {
} }
impl ProcWidgetState { impl ProcWidgetState {
fn new_sort_table(config: &AppConfigFields, colours: &CanvasStyling) -> SortTable { fn new_sort_table(config: &AppConfigFields, palette: &ColourPalette) -> SortTable {
const COLUMNS: [Column<SortTableColumn>; 1] = [Column::hard(SortTableColumn, 7)]; const COLUMNS: [Column<SortTableColumn>; 1] = [Column::hard(SortTableColumn, 7)];
let props = DataTableProps { let props = DataTableProps {
@ -172,13 +170,13 @@ impl ProcWidgetState {
show_table_scroll_position: false, show_table_scroll_position: false,
show_current_entry_when_unfocused: false, show_current_entry_when_unfocused: false,
}; };
let styling = DataTableStyling::from_colours(colours); let styling = DataTableStyling::from_palette(palette);
DataTable::new(COLUMNS, props, styling) DataTable::new(COLUMNS, props, styling)
} }
fn new_process_table( fn new_process_table(
config: &AppConfigFields, colours: &CanvasStyling, columns: Vec<SortColumn<ProcColumn>>, config: &AppConfigFields, colours: &ColourPalette, columns: Vec<SortColumn<ProcColumn>>,
default_index: usize, default_order: SortOrder, default_index: usize, default_order: SortOrder,
) -> ProcessTable { ) -> ProcessTable {
let inner_props = DataTableProps { let inner_props = DataTableProps {
@ -194,14 +192,14 @@ impl ProcWidgetState {
sort_index: default_index, sort_index: default_index,
order: default_order, order: default_order,
}; };
let styling = DataTableStyling::from_colours(colours); let styling = DataTableStyling::from_palette(colours);
DataTable::new_sortable(columns, props, styling) DataTable::new_sortable(columns, props, styling)
} }
pub fn new( pub fn new(
config: &AppConfigFields, mode: ProcWidgetMode, table_config: ProcTableConfig, config: &AppConfigFields, mode: ProcWidgetMode, table_config: ProcTableConfig,
colours: &CanvasStyling, config_columns: &Option<IndexSet<ProcWidgetColumn>>, colours: &ColourPalette, config_columns: &Option<IndexSet<ProcWidgetColumn>>,
) -> Self { ) -> Self {
let process_search_state = { let process_search_state = {
let mut pss = ProcessSearchState::default(); let mut pss = ProcessSearchState::default();
@ -1142,7 +1140,7 @@ mod test {
fn init_state(table_config: ProcTableConfig, columns: &[ProcWidgetColumn]) -> ProcWidgetState { fn init_state(table_config: ProcTableConfig, columns: &[ProcWidgetColumn]) -> ProcWidgetState {
let config = AppConfigFields::default(); let config = AppConfigFields::default();
let styling = CanvasStyling::default(); let styling = ColourPalette::default();
let columns = Some(columns.iter().cloned().collect()); let columns = Some(columns.iter().cloned().collect());
ProcWidgetState::new( ProcWidgetState::new(

View file

@ -4,14 +4,12 @@ use concat_string::concat_string;
use crate::{ use crate::{
app::AppConfigFields, app::AppConfigFields,
canvas::{ canvas::components::data_table::{
components::data_table::{ ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortColumn,
ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortDataTable, SortDataTableProps, SortOrder, SortsRow,
SortColumn, SortDataTable, SortDataTableProps, SortOrder, SortsRow,
},
styling::CanvasStyling,
}, },
data_collection::temperature::TemperatureType, data_collection::temperature::TemperatureType,
options::config::style::ColourPalette,
utils::general::sort_partial_fn, utils::general::sort_partial_fn,
}; };
@ -102,7 +100,7 @@ pub struct TempWidgetState {
} }
impl TempWidgetState { impl TempWidgetState {
pub fn new(config: &AppConfigFields, colours: &CanvasStyling) -> Self { pub(crate) fn new(config: &AppConfigFields, palette: &ColourPalette) -> Self {
let columns = [ let columns = [
SortColumn::soft(TempWidgetColumn::Sensor, Some(0.8)), SortColumn::soft(TempWidgetColumn::Sensor, Some(0.8)),
SortColumn::soft(TempWidgetColumn::Temp, None).default_descending(), SortColumn::soft(TempWidgetColumn::Temp, None).default_descending(),
@ -121,7 +119,7 @@ impl TempWidgetState {
order: SortOrder::Ascending, order: SortOrder::Ascending,
}; };
let styling = DataTableStyling::from_colours(colours); let styling = DataTableStyling::from_palette(palette);
Self { Self {
table: SortDataTable::new_sortable(columns, props, styling), table: SortDataTable::new_sortable(columns, props, styling),

View file

@ -67,3 +67,18 @@ fn test_all_proc() {
fn test_cpu_doughnut() { fn test_cpu_doughnut() {
run_and_kill(&["-C", "./tests/valid_configs/cpu_doughnut.toml"]); run_and_kill(&["-C", "./tests/valid_configs/cpu_doughnut.toml"]);
} }
#[test]
fn test_theme() {
run_and_kill(&["-C", "./tests/valid_configs/theme.toml"]);
}
#[test]
fn test_styling_sanity_check() {
run_and_kill(&["-C", "./tests/valid_configs/styling.toml"]);
}
#[test]
fn test_styling_sanity_check_2() {
run_and_kill(&["-C", "./tests/valid_configs/styling_2.toml"]);
}

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color="#zzzzzz" color="#zzzzzz"

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color="#1111111" color="#1111111"

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color = "#加拿大" color = "#加拿大"

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color = "LightB lue" color = "LightB lue"

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color="257, 50, 50" color="257, 50, 50"

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color="50, 50, 50, 50" color="50, 50, 50, 50"

View file

@ -1,2 +1,2 @@
[colors] [styles.tables.headers]
table_header_color="this is not a colour" color="this is not a colour"

View file

@ -0,0 +1,13 @@
# Test basic colours
[styles.cpu]
all_entry_color="255, 50, 50"
# Test tables
[styles.graphs.legend_text]
color = "#fff"
bg_color = "#000"
bold = false
# Test inline tables
[styles.tables]
headers = { color = "red", bg_color = "reset", bold = true }

View file

@ -0,0 +1,53 @@
# These are all the components that support custom theming. Note that colour support
# will depend on terminal support.
[styles]
# Built-in themes. Valid values are:
# - "default"
# - "default-light"
# - "gruvbox"
# - "gruvbox-light"
# - "nord"
# - "nord-light".
# This will have the lowest precedence if a custom colour palette is set,
# or overriden if the command-line flag for a built-in theme is set.
theme = "default"
[styles.cpu]
all_entry_color = "green"
avg_entry_color = "red"
cpu_core_colors = ["light magenta", "light yellow", "light cyan", "light green", "light blue", "cyan", "green", "blue"]
[styles.memory]
ram = "light magenta"
cache = "light red"
swap = "light yellow"
arc = "light cyan"
gpus = ["light blue", "light red", "cyan", "green", "blue", "red"]
[styles.network]
rx = "light magenta"
tx = "light yellow"
rx_total = "light cyan"
tx_total = "light green"
[styles.battery]
high_battery = "green"
medium_battery = "yellow"
low_battery = "red"
[styles.tables]
headers = {color = "light blue"}
[styles.graphs]
graph_color = "gray"
legend_text = {color = "gray"}
[styles.widgets]
border = "gray"
selected_border = "light blue"
widget_title = {color = "gray"}
text = {color = "gray"}
selected_text = {color = "black", bg_color = "light blue"}
disabled_text = {color = "dark gray"}

View file

@ -0,0 +1,4 @@
#:schema none
[styles]
theme = "gruvbox"