mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 06:34:16 +00:00
feature: support simple colour settings for text fields too (#1511)
* feature: support simple colour settings for text fields too * also add 'color' back to some options * tests
This commit is contained in:
parent
95905d7a2b
commit
f091ebdc6a
15 changed files with 442 additions and 189 deletions
|
@ -83,6 +83,14 @@ 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.
|
||||
|
||||
If you _just_ want to style text by setting the foreground colour, for brevity, then you can also just set the field
|
||||
to be the colour itself. For example:
|
||||
|
||||
```toml
|
||||
[styles.widgets]
|
||||
selected_text = "#fff"
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
#### CPU
|
||||
|
@ -99,34 +107,34 @@ These can be set under `[styles.cpu]`:
|
|||
|
||||
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"]` |
|
||||
| Config field | Details | Examples |
|
||||
| ------------- | ------------------------------------------------------------------------------ | --------------------------------------- |
|
||||
| `ram_color` | The colour of the RAM label and graph line | `ram_color = "Red"` |
|
||||
| `cache_color` | The colour of the cache label and graph line. Does not do anything on Windows. | `cache_color = "#ffffff"` |
|
||||
| `swap_color` | The colour of the swap label and graph line | `swap_color = "255, 0, 255"` |
|
||||
| `arc_color` | The colour of the ARC label and graph line | `arc_color = "Blue"` |
|
||||
| `gpu_colors` | Colour of each GPU's memory label and graph line. Read in order. | `gpu_colors = ["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"` |
|
||||
| Config field | Details | Examples |
|
||||
| ---------------- | --------------------------------------------------------- | ---------------------------- |
|
||||
| `rx_color` | The colour of the RX (download) label and graph line | `rx_color = "Red"` |
|
||||
| `tx_color` | The colour of the TX (upload) label and graph line | `tx_color = "#ffffff"` |
|
||||
| `rx_total_color` | The colour of the total RX (download) label in basic mode | `rx_total_color = "0, 0, 0"` |
|
||||
| `tx_total_color` | The colour of the total TX (upload) label in basic mode | `tx_total_color = "#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"` |
|
||||
| Config field | Details | Examples |
|
||||
| ---------------------- | ------------------------------------------------------------------------ | ---------------------------------- |
|
||||
| `high_battery_color` | The colour of the battery widget bar when the battery is over 50% | `high_battery_color = "Red"` |
|
||||
| `medium_battery_color` | The colour of the battery widget bar when the battery between 10% to 50% | `medium_battery_color = "#ffffff"` |
|
||||
| `low_battery_color` | The colour of the battery widget bar when the battery is under 10% | `low_battery_color = "0, 0, 0"` |
|
||||
|
||||
#### Tables
|
||||
|
||||
|
@ -149,11 +157,11 @@ These can be set under `[styles.graphs]`:
|
|||
|
||||
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 }` |
|
||||
| Config field | Details | Examples |
|
||||
| ----------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------- |
|
||||
| `border_color` | The colour of the widgets' borders | `border_color = "white"` |
|
||||
| `selected_border_color` | The colour of a widget's borders when the widget is selected | `selected_border_color = "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 that is selected | `selected_text = { color = "black", bg_color = "blue", bold = true }` |
|
||||
| `disabled_text` | Text styling for text when representing something that is disabled | `disabled_text = { color = "black", bg_color = "blue", bold = true }` |
|
||||
|
|
|
@ -147,22 +147,22 @@
|
|||
#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"]
|
||||
#ram_color = "light magenta"
|
||||
#cache_color = "light red"
|
||||
#swap_color = "light yellow"
|
||||
#arc_color = "light cyan"
|
||||
#gpu_colors = ["light blue", "light red", "cyan", "green", "blue", "red"]
|
||||
|
||||
#[styles.network]
|
||||
#rx = "light magenta"
|
||||
#tx = "light yellow"
|
||||
#rx_total = "light cyan"
|
||||
#tx_total = "light green"
|
||||
#rx_color = "light magenta"
|
||||
#tx_color = "light yellow"
|
||||
#rx_total_color = "light cyan"
|
||||
#tx_total_color = "light green"
|
||||
|
||||
#[styles.battery]
|
||||
#high_battery = "green"
|
||||
#medium_battery = "yellow"
|
||||
#low_battery = "red"
|
||||
#high_battery_color = "green"
|
||||
#medium_battery_color = "yellow"
|
||||
#low_battery_color = "red"
|
||||
|
||||
#[styles.tables]
|
||||
#headers = {color = "light blue"}
|
||||
|
@ -172,8 +172,8 @@
|
|||
#legend_text = {color = "gray"}
|
||||
|
||||
#[styles.widgets]
|
||||
#border = "gray"
|
||||
#selected_border = "light blue"
|
||||
#border_color = "gray"
|
||||
#selected_border_color = "light blue"
|
||||
#widget_title = {color = "gray"}
|
||||
#text = {color = "gray"}
|
||||
#selected_text = {color = "black", bg_color = "light blue"}
|
||||
|
|
|
@ -90,7 +90,8 @@
|
|||
"description": "Styling specific to the battery widget.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"high_battery": {
|
||||
"high_battery_color": {
|
||||
"description": "The colour of the battery widget bar when the battery is over 50%.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -100,7 +101,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"low_battery": {
|
||||
"low_battery_color": {
|
||||
"description": "The colour of the battery widget bar when the battery is under 10%.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -110,7 +112,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"medium_battery": {
|
||||
"medium_battery_color": {
|
||||
"description": "The colour of the battery widget bar when the battery between 10% to 50%.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -147,6 +150,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"all_entry_color": {
|
||||
"description": "The colour of the \"All\" CPU label.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -157,6 +161,7 @@
|
|||
]
|
||||
},
|
||||
"avg_entry_color": {
|
||||
"description": "The colour of the average CPU label and graph line.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -167,6 +172,7 @@
|
|||
]
|
||||
},
|
||||
"cpu_core_colors": {
|
||||
"description": "Colour of each CPU threads' label and graph line. Read in order.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
|
@ -481,6 +487,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"graph_color": {
|
||||
"description": "The general colour of the parts of the graph.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -491,6 +498,7 @@
|
|||
]
|
||||
},
|
||||
"legend_text": {
|
||||
"description": "Text styling for graph's legend text.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TextStyleConfig"
|
||||
|
@ -536,7 +544,8 @@
|
|||
"description": "Styling specific to the memory widget.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"arc": {
|
||||
"arc_color": {
|
||||
"description": "The colour of the ARC label and graph line.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -546,7 +555,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"cache": {
|
||||
"cache_color": {
|
||||
"description": "The colour of the cache label and graph line. Does not do anything on Windows.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -556,7 +566,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"gpus": {
|
||||
"gpu_colors": {
|
||||
"description": "Colour of each GPU's memory label and graph line. Read in order.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
|
@ -565,7 +576,8 @@
|
|||
"$ref": "#/definitions/ColorStr"
|
||||
}
|
||||
},
|
||||
"ram": {
|
||||
"ram_color": {
|
||||
"description": "The colour of the RAM label and graph line.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -575,7 +587,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"swap": {
|
||||
"swap_color": {
|
||||
"description": "The colour of the swap label and graph line.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -608,7 +621,8 @@
|
|||
"description": "Styling specific to the network widget.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rx": {
|
||||
"rx_color": {
|
||||
"description": "The colour of the RX (download) label and graph line.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -618,8 +632,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"rx_total": {
|
||||
"description": "Set the colour of the \"rx total\" text. This only affects basic mode.",
|
||||
"rx_total_color": {
|
||||
"description": "he colour of the total RX (download) label in basic mode.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -629,7 +643,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"tx": {
|
||||
"tx_color": {
|
||||
"description": "The colour of the TX (upload) label and graph line.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -639,8 +654,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"tx_total": {
|
||||
"description": "Set the colour of the \"tx total\" text. This only affects basic mode.",
|
||||
"tx_total_color": {
|
||||
"description": "The colour of the total TX (upload) label in basic mode.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -831,6 +846,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"headers": {
|
||||
"description": "Text styling for table headers.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TextStyleConfig"
|
||||
|
@ -861,44 +877,52 @@
|
|||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
},
|
||||
"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": "object",
|
||||
"properties": {
|
||||
"bg_color": {
|
||||
"description": "A built-in ANSI colour, RGB hex, or RGB colour code.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"border_color": {
|
||||
"description": "The colour of the widgets' borders.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -909,6 +933,7 @@
|
|||
]
|
||||
},
|
||||
"disabled_text": {
|
||||
"description": "Text styling for text when representing something that is disabled.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TextStyleConfig"
|
||||
|
@ -918,7 +943,8 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"selected_border": {
|
||||
"selected_border_color": {
|
||||
"description": "The colour of a widget's borders when the widget is selected.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ColorStr"
|
||||
|
@ -929,6 +955,7 @@
|
|||
]
|
||||
},
|
||||
"selected_text": {
|
||||
"description": "Text styling for text when representing something that is selected.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TextStyleConfig"
|
||||
|
@ -939,6 +966,7 @@
|
|||
]
|
||||
},
|
||||
"text": {
|
||||
"description": "Text styling for text in general.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TextStyleConfig"
|
||||
|
@ -949,6 +977,7 @@
|
|||
]
|
||||
},
|
||||
"widget_title": {
|
||||
"description": "Text styling for a widget's title.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TextStyleConfig"
|
||||
|
|
|
@ -411,22 +411,22 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
|
|||
#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"]
|
||||
#ram_color = "light magenta"
|
||||
#cache_color = "light red"
|
||||
#swap_color = "light yellow"
|
||||
#arc_color = "light cyan"
|
||||
#gpu_colors = ["light blue", "light red", "cyan", "green", "blue", "red"]
|
||||
|
||||
#[styles.network]
|
||||
#rx = "light magenta"
|
||||
#tx = "light yellow"
|
||||
#rx_total = "light cyan"
|
||||
#tx_total = "light green"
|
||||
#rx_color = "light magenta"
|
||||
#tx_color = "light yellow"
|
||||
#rx_total_color = "light cyan"
|
||||
#tx_total_color = "light green"
|
||||
|
||||
#[styles.battery]
|
||||
#high_battery = "green"
|
||||
#medium_battery = "yellow"
|
||||
#low_battery = "red"
|
||||
#high_battery_color = "green"
|
||||
#medium_battery_color = "yellow"
|
||||
#low_battery_color = "red"
|
||||
|
||||
#[styles.tables]
|
||||
#headers = {color = "light blue"}
|
||||
|
@ -436,8 +436,8 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
|
|||
#legend_text = {color = "gray"}
|
||||
|
||||
#[styles.widgets]
|
||||
#border = "gray"
|
||||
#selected_border = "light blue"
|
||||
#border_color = "gray"
|
||||
#selected_border_color = "light blue"
|
||||
#widget_title = {color = "gray"}
|
||||
#text = {color = "gray"}
|
||||
#selected_text = {color = "black", bg_color = "light blue"}
|
||||
|
|
|
@ -32,20 +32,24 @@ use super::Config;
|
|||
pub(crate) struct ColorStr(Cow<'static, str>);
|
||||
|
||||
/// A style for text.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[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>,
|
||||
pub(crate) enum TextStyleConfig {
|
||||
Colour(ColorStr),
|
||||
TextStyle {
|
||||
/// A built-in ANSI colour, RGB hex, or RGB colour code.
|
||||
#[serde(alias = "colour")]
|
||||
color: Option<ColorStr>,
|
||||
|
||||
/// A built-in ANSI colour, RGB hex, or RGB colour code.
|
||||
#[serde(alias = "bg_colour")]
|
||||
pub(crate) bg_color: Option<ColorStr>,
|
||||
/// A built-in ANSI colour, RGB hex, or RGB colour code.
|
||||
#[serde(alias = "bg_colour")]
|
||||
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>,
|
||||
/// Whether to make this text bolded or not. If not set,
|
||||
/// will default to built-in defaults.
|
||||
bold: Option<bool>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Style-related configs.
|
||||
|
@ -161,28 +165,28 @@ impl ColourPalette {
|
|||
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);
|
||||
set_colour!(self.ram_style, config.memory, ram_color);
|
||||
set_colour!(self.swap_style, config.memory, swap_color);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
set_colour!(self.cache_style, config.memory, cache);
|
||||
set_colour!(self.cache_style, config.memory, cache_color);
|
||||
|
||||
#[cfg(feature = "zfs")]
|
||||
set_colour!(self.arc_style, config.memory, arc);
|
||||
set_colour!(self.arc_style, config.memory, arc_color);
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
set_colour_list!(self.gpu_colours, config.memory, gpus);
|
||||
set_colour_list!(self.gpu_colours, config.memory, gpu_colors);
|
||||
|
||||
// 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);
|
||||
set_colour!(self.rx_style, config.network, rx_color);
|
||||
set_colour!(self.tx_style, config.network, tx_color);
|
||||
set_colour!(self.total_rx_style, config.network, rx_total_color);
|
||||
set_colour!(self.total_tx_style, config.network, tx_total_color);
|
||||
|
||||
// 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);
|
||||
set_colour!(self.high_battery, config.battery, high_battery_color);
|
||||
set_colour!(self.medium_battery, config.battery, medium_battery_color);
|
||||
set_colour!(self.low_battery, config.battery, low_battery_color);
|
||||
|
||||
// Tables
|
||||
set_style!(self.table_header_style, config.tables, headers);
|
||||
|
@ -198,11 +202,11 @@ impl ColourPalette {
|
|||
set_style!(self.disabled_text_style, config.widgets, disabled_text);
|
||||
|
||||
// Widget borders
|
||||
set_colour!(self.border_style, config.widgets, border);
|
||||
set_colour!(self.border_style, config.widgets, border_color);
|
||||
set_colour!(
|
||||
self.highlighted_border_style,
|
||||
config.widgets,
|
||||
selected_border
|
||||
selected_border_color
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -6,7 +6,15 @@ use super::ColorStr;
|
|||
#[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>,
|
||||
/// The colour of the battery widget bar when the battery is over 50%.
|
||||
#[serde(alias = "high_battery_colour")]
|
||||
pub(crate) high_battery_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the battery widget bar when the battery between 10% to 50%.
|
||||
#[serde(alias = "medium_battery_colour")]
|
||||
pub(crate) medium_battery_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the battery widget bar when the battery is under 10%.
|
||||
#[serde(alias = "low_battery_colour")]
|
||||
pub(crate) low_battery_color: Option<ColorStr>,
|
||||
}
|
||||
|
|
|
@ -6,13 +6,15 @@ use super::ColorStr;
|
|||
#[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?
|
||||
/// The colour of the "All" CPU label.
|
||||
#[serde(alias = "all_entry_colour")]
|
||||
pub(crate) all_entry_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the average CPU label and graph line.
|
||||
#[serde(alias = "avg_entry_colour")]
|
||||
pub(crate) avg_entry_color: Option<ColorStr>,
|
||||
|
||||
/// Colour of each CPU threads' label and graph line. Read in order.
|
||||
#[serde(alias = "cpu_core_colours")]
|
||||
pub(crate) cpu_core_colors: Option<Vec<ColorStr>>,
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ use super::{ColorStr, TextStyleConfig};
|
|||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
pub(crate) struct GraphStyle {
|
||||
/// The general colour of the parts of the graph.
|
||||
#[serde(alias = "graph_colour")]
|
||||
pub(crate) graph_color: Option<ColorStr>,
|
||||
|
||||
/// Text styling for graph's legend text.
|
||||
pub(crate) legend_text: Option<TextStyleConfig>,
|
||||
}
|
||||
|
|
|
@ -1,15 +1,30 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::ColorStr;
|
||||
// TODO: Maybe I should swap the alias and the field name since internally I use u.
|
||||
|
||||
/// 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>,
|
||||
/// The colour of the RAM label and graph line.
|
||||
#[serde(alias = "ram_colour")]
|
||||
pub(crate) ram_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the cache label and graph line. Does not do anything on Windows.
|
||||
#[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>>,
|
||||
#[serde(alias = "cache_colour")]
|
||||
pub(crate) cache_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the swap label and graph line.
|
||||
#[serde(alias = "swap_colour")]
|
||||
pub(crate) swap_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the ARC label and graph line.
|
||||
#[serde(alias = "arc_colour")]
|
||||
pub(crate) arc_color: Option<ColorStr>,
|
||||
|
||||
/// Colour of each GPU's memory label and graph line. Read in order.
|
||||
#[serde(alias = "gpu_colours")]
|
||||
pub(crate) gpu_colors: Option<Vec<ColorStr>>,
|
||||
}
|
||||
|
|
|
@ -6,14 +6,19 @@ use super::ColorStr;
|
|||
#[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>,
|
||||
/// The colour of the RX (download) label and graph line.
|
||||
#[serde(alias = "rx_colour")]
|
||||
pub(crate) rx_color: Option<ColorStr>,
|
||||
|
||||
/// Set the colour of the "rx total" text. This only affects
|
||||
/// basic mode.
|
||||
pub(crate) rx_total: Option<ColorStr>,
|
||||
/// The colour of the TX (upload) label and graph line.
|
||||
#[serde(alias = "tx_colour")]
|
||||
pub(crate) tx_color: Option<ColorStr>,
|
||||
|
||||
/// Set the colour of the "tx total" text. This only affects
|
||||
/// basic mode.
|
||||
pub(crate) tx_total: Option<ColorStr>,
|
||||
/// he colour of the total RX (download) label in basic mode.
|
||||
#[serde(alias = "rx_total_colour")]
|
||||
pub(crate) rx_total_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of the total TX (upload) label in basic mode.
|
||||
#[serde(alias = "tx_total_colour")]
|
||||
pub(crate) tx_total_color: Option<ColorStr>,
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ use super::TextStyleConfig;
|
|||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
pub(crate) struct TableStyle {
|
||||
/// Text styling for table headers.
|
||||
pub(crate) headers: Option<TextStyleConfig>,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use concat_string::concat_string;
|
||||
use tui::style::{Color, Style};
|
||||
use tui::style::Color;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
/// Convert a hex string to a colour.
|
||||
|
@ -53,10 +53,6 @@ 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> {
|
||||
let rgb_list = rgb_str.split(',').collect::<Vec<&str>>();
|
||||
if rgb_list.len() != 3 {
|
||||
|
@ -136,18 +132,55 @@ macro_rules! opt {
|
|||
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)
|
||||
)),
|
||||
})?;
|
||||
match &style {
|
||||
TextStyleConfig::Colour(colour) => {
|
||||
$palette_field = $palette_field.fg(
|
||||
crate::options::config::style::utils::str_to_colour(&colour.0)
|
||||
.map_err(|err| match stringify!($config_location).split_once(".") {
|
||||
Some((_, loc)) => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{loc}.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
None => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
})?
|
||||
);
|
||||
}
|
||||
TextStyleConfig::TextStyle {color, bg_color, bold: _} => {
|
||||
if let Some(fg) = &color {
|
||||
$palette_field = $palette_field.fg(
|
||||
crate::options::config::style::utils::str_to_colour(&fg.0)
|
||||
.map_err(|err| match stringify!($config_location).split_once(".") {
|
||||
Some((_, loc)) => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{loc}.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
None => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
})?
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(bg) = &bg_color {
|
||||
$palette_field = $palette_field.bg(
|
||||
crate::options::config::style::utils::str_to_colour(&bg.0)
|
||||
.map_err(|err| match stringify!($config_location).split_once(".") {
|
||||
Some((_, loc)) => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{loc}.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
None => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
})?
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -156,19 +189,20 @@ macro_rules! set_style {
|
|||
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| {
|
||||
$palette_field = $palette_field.fg(
|
||||
crate::options::config::style::utils::str_to_colour(&colour.0).map_err(|err| {
|
||||
match stringify!($config_location).split_once(".") {
|
||||
Some((_, loc)) => OptionError::config(format!(
|
||||
Some((_, loc)) => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{loc}.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
None => OptionError::config(format!(
|
||||
None => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
}
|
||||
})?;
|
||||
})?,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -178,14 +212,17 @@ macro_rules! set_colour_list {
|
|||
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))
|
||||
.map(|s| {
|
||||
Ok(Style::default()
|
||||
.fg(crate::options::config::style::utils::str_to_colour(&s.0)?))
|
||||
})
|
||||
.collect::<Result<Vec<Style>, String>>()
|
||||
.map_err(|err| match stringify!($config_location).split_once(".") {
|
||||
Some((_, loc)) => OptionError::config(format!(
|
||||
Some((_, loc)) => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{loc}.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
None => OptionError::config(format!(
|
||||
None => crate::options::OptionError::config(format!(
|
||||
"Please update 'styles.{}' in your config file. {err}",
|
||||
stringify!($field)
|
||||
)),
|
||||
|
@ -199,6 +236,10 @@ pub(super) use {opt, set_colour, set_colour_list, set_style};
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use tui::style::Style;
|
||||
|
||||
use crate::options::config::style::{ColorStr, TextStyleConfig};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -351,4 +392,124 @@ mod test {
|
|||
assert!(convert_rgb_to_color("1, -100000, 1").is_err());
|
||||
assert!(convert_rgb_to_color("1, -100000, 100000").is_err());
|
||||
}
|
||||
|
||||
struct DummyConfig {
|
||||
inner: Option<InnerDummyConfig>,
|
||||
}
|
||||
|
||||
struct InnerDummyConfig {
|
||||
color_a: Option<ColorStr>,
|
||||
color_b: Option<ColorStr>,
|
||||
color_c: Option<ColorStr>,
|
||||
color_d: Option<ColorStr>,
|
||||
many_colors: Option<Vec<ColorStr>>,
|
||||
text_a: Option<TextStyleConfig>,
|
||||
text_b: Option<TextStyleConfig>,
|
||||
text_c: Option<TextStyleConfig>,
|
||||
text_d: Option<TextStyleConfig>,
|
||||
text_e: Option<TextStyleConfig>,
|
||||
}
|
||||
|
||||
impl Default for InnerDummyConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color_a: None,
|
||||
color_b: Some(ColorStr("red".into())),
|
||||
color_c: Some(ColorStr("255, 255, 255".into())),
|
||||
color_d: Some(ColorStr("#000000".into())),
|
||||
many_colors: Some(vec![ColorStr("red".into()), ColorStr("blue".into())]),
|
||||
text_a: Some(TextStyleConfig::Colour(ColorStr("green".into()))),
|
||||
text_b: Some(TextStyleConfig::TextStyle {
|
||||
color: None,
|
||||
bg_color: None,
|
||||
bold: None,
|
||||
}),
|
||||
text_c: Some(TextStyleConfig::TextStyle {
|
||||
color: Some(ColorStr("magenta".into())),
|
||||
bg_color: Some(ColorStr("255, 255, 255".into())),
|
||||
bold: Some(false),
|
||||
}),
|
||||
text_d: Some(TextStyleConfig::TextStyle {
|
||||
color: Some(ColorStr("#fff".into())),
|
||||
bg_color: Some(ColorStr("1, 1, 1".into())),
|
||||
bold: Some(true),
|
||||
}),
|
||||
text_e: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_colour() -> anyhow::Result<()> {
|
||||
let mut s = Style::default().fg(Color::Black);
|
||||
let dummy = DummyConfig {
|
||||
inner: Some(InnerDummyConfig::default()),
|
||||
};
|
||||
|
||||
set_colour!(s, &dummy.inner, color_a);
|
||||
assert_eq!(s.fg.unwrap(), Color::Black);
|
||||
assert_eq!(s.bg, None);
|
||||
|
||||
set_colour!(s, &dummy.inner, color_b);
|
||||
assert_eq!(s.fg.unwrap(), Color::Red);
|
||||
assert_eq!(s.bg, None);
|
||||
|
||||
set_colour!(s, &dummy.inner, color_c);
|
||||
assert_eq!(s.fg.unwrap(), Color::Rgb(255, 255, 255));
|
||||
assert_eq!(s.bg, None);
|
||||
|
||||
set_colour!(s, &dummy.inner, color_d);
|
||||
assert_eq!(s.fg.unwrap(), Color::Rgb(0, 0, 0));
|
||||
assert_eq!(s.bg, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_multi_colours() -> anyhow::Result<()> {
|
||||
let mut s: Vec<Style> = vec![];
|
||||
let dummy = DummyConfig {
|
||||
inner: Some(InnerDummyConfig::default()),
|
||||
};
|
||||
|
||||
set_colour_list!(s, &dummy.inner, many_colors);
|
||||
assert_eq!(s.len(), 2);
|
||||
assert_eq!(s[0].fg, Some(Color::Red));
|
||||
assert_eq!(s[1].fg, Some(Color::Blue));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_style() -> anyhow::Result<()> {
|
||||
let mut s = Style::default().fg(Color::Black);
|
||||
let dummy = DummyConfig {
|
||||
inner: Some(InnerDummyConfig::default()),
|
||||
};
|
||||
|
||||
set_style!(s, &dummy.inner, text_e);
|
||||
assert_eq!(s.fg.unwrap(), Color::Black);
|
||||
assert_eq!(s.bg, None);
|
||||
assert!(s.add_modifier.is_empty());
|
||||
|
||||
set_style!(s, &dummy.inner, text_a);
|
||||
assert_eq!(s.fg.unwrap(), Color::Green);
|
||||
assert_eq!(s.bg, None);
|
||||
|
||||
set_style!(s, &dummy.inner, text_b);
|
||||
assert_eq!(s.fg.unwrap(), Color::Green);
|
||||
assert_eq!(s.bg, None);
|
||||
|
||||
set_style!(s, &dummy.inner, text_c);
|
||||
assert_eq!(s.fg.unwrap(), Color::Magenta);
|
||||
assert_eq!(s.bg.unwrap(), Color::Rgb(255, 255, 255));
|
||||
|
||||
set_style!(s, &dummy.inner, text_d);
|
||||
assert_eq!(s.fg.unwrap(), Color::Rgb(255, 255, 255));
|
||||
assert_eq!(s.bg.unwrap(), Color::Rgb(1, 1, 1));
|
||||
// TODO: Add this
|
||||
// assert!(s.add_modifier.contains(Modifier::BOLD));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,23 @@ use super::{ColorStr, TextStyleConfig};
|
|||
#[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>,
|
||||
/// The colour of the widgets' borders.
|
||||
#[serde(alias = "border_colour")]
|
||||
pub(crate) border_color: Option<ColorStr>,
|
||||
|
||||
/// The colour of a widget's borders when the widget is selected.
|
||||
#[serde(alias = "selected_border_colour")]
|
||||
pub(crate) selected_border_color: Option<ColorStr>,
|
||||
|
||||
/// Text styling for a widget's title.
|
||||
pub(crate) widget_title: Option<TextStyleConfig>,
|
||||
|
||||
/// Text styling for text in general.
|
||||
pub(crate) text: Option<TextStyleConfig>,
|
||||
|
||||
/// Text styling for text when representing something that is selected.
|
||||
pub(crate) selected_text: Option<TextStyleConfig>,
|
||||
|
||||
/// Text styling for text when representing something that is disabled.
|
||||
pub(crate) disabled_text: Option<TextStyleConfig>,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Test basic colours
|
||||
[styles.cpu]
|
||||
all_entry_color="255, 50, 50"
|
||||
all_entry_color = "255, 50, 50"
|
||||
|
||||
# Test tables
|
||||
[styles.graphs.legend_text]
|
||||
|
@ -10,4 +10,10 @@ bold = false
|
|||
|
||||
# Test inline tables
|
||||
[styles.tables]
|
||||
headers = { color = "red", bg_color = "reset", bold = true }
|
||||
headers = { color = "red", bg_color = "black", bold = true }
|
||||
|
||||
# Test using normal colour where inline table can also work
|
||||
[styles.widgets]
|
||||
selected_text = "#fff"
|
||||
disabled_text = "blue"
|
||||
text = "255, 0, 255"
|
||||
|
|
|
@ -20,34 +20,34 @@ 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"]
|
||||
ram_color = "light magenta"
|
||||
cache_color = "light red"
|
||||
swap_color = "light yellow"
|
||||
arc_color = "light cyan"
|
||||
gpu_colors = ["light blue", "light red", "cyan", "green", "blue", "red"]
|
||||
|
||||
[styles.network]
|
||||
rx = "light magenta"
|
||||
tx = "light yellow"
|
||||
rx_total = "light cyan"
|
||||
tx_total = "light green"
|
||||
rx_color = "light magenta"
|
||||
tx_color = "light yellow"
|
||||
rx_total_color = "light cyan"
|
||||
tx_total_color = "light green"
|
||||
|
||||
[styles.battery]
|
||||
high_battery = "green"
|
||||
medium_battery = "yellow"
|
||||
low_battery = "red"
|
||||
high_battery_color = "green"
|
||||
medium_battery_color = "yellow"
|
||||
low_battery_color = "red"
|
||||
|
||||
[styles.tables]
|
||||
headers = {color = "light blue"}
|
||||
headers = { color = "light blue" }
|
||||
|
||||
[styles.graphs]
|
||||
graph_color = "gray"
|
||||
legend_text = {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"}
|
||||
border_color = "gray"
|
||||
selected_border_color = "light blue"
|
||||
widget_title = { color = "gray" }
|
||||
text = { color = "gray" }
|
||||
selected_text = { color = "black", bg_color = "light blue" }
|
||||
disabled_text = { color = "dark gray" }
|
||||
|
|
Loading…
Reference in a new issue