feature: add more pipe gauge bar fill options (#2245)

Adds block and square character options for pipe gauge widgets.

May still redesign this (maybe add option for a background), but this is fine for now.
This commit is contained in:
Clement Tsang
2026-09-12 16:28:29 -04:00
committed by GitHub
parent e4c035f845
commit 875e892687
18 changed files with 370 additions and 59 deletions
+1
View File
@@ -29,6 +29,7 @@ That said, these are more guidelines rather than hard rules, though the project
### Features ### Features
- [#2239](https://github.com/ClementTsang/bottom/pull/2239): Initial Intel GPU support for Linux to get process GPU usage. - [#2239](https://github.com/ClementTsang/bottom/pull/2239): Initial Intel GPU support for Linux to get process GPU usage.
- [#1434](https://github.com/ClementTsang/bottom/issues/1434): Support solid bars using block and square characters via `styles.widgets.bar_type`.
### Other ### Other
@@ -187,13 +187,14 @@ These can be set under `[styles.graphs]`:
These can be set under `[styles.widgets]`: These can be set under `[styles.widgets]`:
| Config field | Details | Examples | | Config field | Details | Examples |
| ------------------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | ------------------------ | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `border_colour` | The colour of the widgets' borders | `border_colour = "white"` | | `border_colour` | The colour of the widgets' borders | `border_colour = "white"` |
| `selected_border_colour` | The colour of a widget's borders when the widget is selected | `selected_border_colour = "white"` | | `selected_border_colour` | The colour of a widget's borders when the widget is selected | `selected_border_colour = "white"` |
| `widget_title` | Text styling for a widget's title | `widget_title = { colour = "black", bg_colour = "blue", bold = true }` | | `widget_title` | Text styling for a widget's title | `widget_title = { colour = "black", bg_colour = "blue", bold = true }` |
| `bg_colour` | The background colour of the widgets | `bg_colour = "black"` | | `bg_colour` | The background colour of the widgets | `bg_colour = "black"` |
| `text` | Text styling for text in general | `text = { colour = "black", bg_colour = "blue", bold = true }` | | `text` | Text styling for text in general | `text = { colour = "black", bg_colour = "blue", bold = true }` |
| `selected_text` | Text styling for text when representing something that is selected | `selected_text = { colour = "black", bg_colour = "blue", bold = true }` | | `selected_text` | Text styling for text when representing something that is selected | `selected_text = { colour = "black", bg_colour = "blue", bold = true }` |
| `disabled_text` | Text styling for text when representing something that is disabled | `disabled_text = { colour = "black", bg_colour = "blue", bold = true }` | | `disabled_text` | Text styling for text when representing something that is disabled | `disabled_text = { colour = "black", bg_colour = "blue", bold = true }` |
| `thread_text` | Text styling for text when representing process thread, only usable on Linux at the moment | `thread_text = { colour = "green", bg_colour = "blue", bold = true }` | | `thread_text` | Text styling for text when representing process thread, only usable on Linux at the moment | `thread_text = { colour = "green", bg_colour = "blue", bold = true }` |
| `bar_type` | What to fill bar widgets with (e.g. basic CPU/memory); either `"pipe"` (default), `"block"`, or `"square"` | `bar_type = "block"` |
+4 -2
View File
@@ -474,10 +474,12 @@
#text = {colour = "gray"} #text = {colour = "gray"}
#selected_text = {colour = "black", bg_colour = "light blue"} #selected_text = {colour = "black", bg_colour = "light blue"}
#disabled_text = {colour = "dark gray"} #disabled_text = {colour = "dark gray"}
# Disabled by default # Disabled by default.
#bg_colour = "black" #bg_colour = "black"
# Only on Linux # Only supported on Linux.
#thread_text = {colour = "green"} #thread_text = {colour = "green"}
# One of "pipe", "block", or "square".
#bar_type = "pipe"
# 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.
+46 -15
View File
@@ -268,7 +268,7 @@
] ]
}, },
"include_unmounted": { "include_unmounted": {
"description": "Whether to include block devices that aren't currently mounted (currently Linux only). Defaults to false.", "description": "Whether to include block devices that aren't currently mounted\n(currently Linux only). Defaults to false.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
@@ -322,14 +322,14 @@
"type": "object", "type": "object",
"properties": { "properties": {
"include_unmounted": { "include_unmounted": {
"description": "Whether to include block devices that aren't currently mounted (currently Linux only). Defaults to false.", "description": "Whether to include block devices that aren't currently mounted\n(currently Linux only). Defaults to false.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
] ]
}, },
"legend": { "legend": {
"description": "Whether to label legend entries by device name or mount point. Defaults to disk name.", "description": "Whether to label legend entries by device name or mount point. Defaults\nto disk name.",
"anyOf": [ "anyOf": [
{ {
"$ref": "#/$defs/DiskGraphLegend" "$ref": "#/$defs/DiskGraphLegend"
@@ -812,7 +812,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"cache_memory": { "cache_memory": {
"description": "Whether to collect and display cache and buffer memory. Not available on Windows.", "description": "Whether to collect and display cache and buffer memory. Not available on\nWindows.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
@@ -931,14 +931,14 @@
] ]
}, },
"start_zeroed": { "start_zeroed": {
"description": "Zeroes out the total network usage (\"All\") counters so it shows the total usage\nsince the app is started, rather than the total usage since boot.", "description": "Zeroes out the total network usage (\"All\") counters so it shows the\ntotal usage since the app is started, rather than the total usage\nsince boot.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
] ]
}, },
"use_binary_prefix": { "use_binary_prefix": {
"description": "Displays the network widget with a binary prefix (e.g. kibibits) rather than a decimal\nprefix (e.g. kilobits). Defaults to decimal prefixes.", "description": "Displays the network widget with a binary prefix (e.g. kibibits) rather\nthan a decimal prefix (e.g. kilobits). Defaults to decimal prefixes.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
@@ -952,7 +952,7 @@
] ]
}, },
"use_log": { "use_log": {
"description": "Displays the network widget with a log scale. Defaults to a non-log scale.", "description": "Displays the network widget with a log scale. Defaults to a non-log\nscale.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
@@ -1101,14 +1101,14 @@
} }
}, },
"current_usage": { "current_usage": {
"description": "Calculates process CPU usage as a percentage of current usage rather than total usage.", "description": "Calculates process CPU usage as a percentage of current usage rather\nthan total usage.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
] ]
}, },
"default_grouped": { "default_grouped": {
"description": "Groups processes with the same name by default. No effect if `--tree` is set.", "description": "Groups processes with the same name by default. No effect if `--tree` is\nset.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
@@ -1140,7 +1140,7 @@
] ]
}, },
"disable_advanced_kill": { "disable_advanced_kill": {
"description": "Disable the advanced kill dialog and just show the basic one with no options.", "description": "Disable the advanced kill dialog and just show the basic one with no\noptions.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
@@ -1441,7 +1441,7 @@
"default": null "default": null
}, },
"max_temp": { "max_temp": {
"description": "An upper temperature value for the graph; entries higher than this will be hidden. If not set,\nthere is no limit.\n\nIs in the configured temperature unit.", "description": "An upper temperature value for the graph; entries higher than this will\nbe hidden. If not set, there is no limit.\n\nIs in the configured temperature unit.",
"type": [ "type": [
"number", "number",
"null" "null"
@@ -1538,19 +1538,50 @@
} }
] ]
}, },
"WidgetBarType": {
"description": "The type of character used to fill in bars, such as the ones used by the\nbasic CPU and memory widgets.",
"oneOf": [
{
"description": "Fill bars with pipe characters (`|`).",
"type": "string",
"const": "pipe"
},
{
"description": "Fill bars with block characters (`█`, `▉`, etc.).",
"type": "string",
"const": "block"
},
{
"description": "Fill bars with square characters (`■`).",
"type": "string",
"const": "square"
}
]
},
"WidgetBorderType": { "WidgetBorderType": {
"type": "string", "type": "string",
"enum": [ "enum": [
"Default", "default",
"Rounded", "rounded",
"Double", "double",
"Thick" "thick"
] ]
}, },
"WidgetStyle": { "WidgetStyle": {
"description": "General styling for generic widgets.", "description": "General styling for generic widgets.",
"type": "object", "type": "object",
"properties": { "properties": {
"bar_type": {
"description": "What character to fill in bars with, such as the ones used by the basic\nCPU and memory widgets.",
"anyOf": [
{
"$ref": "#/$defs/WidgetBarType"
},
{
"type": "null"
}
]
},
"bg_colour": { "bg_colour": {
"description": "Background colour for widgets.", "description": "Background colour for widgets.",
"anyOf": [ "anyOf": [
+2
View File
@@ -44,6 +44,8 @@ macro_rules! generate_column_schemas {
} }
fn generate_schema(schema_options: SchemaOptions) -> anyhow::Result<()> { fn generate_schema(schema_options: SchemaOptions) -> anyhow::Result<()> {
// TODO: remove newlines in descriptions?
let mut schema = schemars::schema_for!(config::Config); let mut schema = schemars::schema_for!(config::Config);
{ {
// TODO: Maybe make this case insensitive? See https://stackoverflow.com/a/68639341 // TODO: Maybe make this case insensitive? See https://stackoverflow.com/a/68639341
+177 -12
View File
@@ -2,6 +2,7 @@ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::Rect, layout::Rect,
style::Style, style::Style,
symbols,
text::Line, text::Line,
widgets::{Block, Widget}, widgets::{Block, Widget},
}; };
@@ -16,7 +17,41 @@ pub enum LabelLimit {
StartLabel, StartLabel,
} }
/// A widget to measure something, using pipe characters ('|') as a unit. /// What bar character type to use.
#[derive(Debug, Clone, Default, Copy)]
pub enum BarType {
#[default]
/// The pipe character (`|`)
Pipe,
/// Block characters (`█`, `▉`, etc.)
Block,
/// Square characters (`■`)
Square,
}
impl BarType {
#[inline]
fn is_block(&self) -> bool {
matches!(self, BarType::Block)
}
}
fn get_unicode_block(frac: f64) -> &'static str {
match (frac * 8.0).round() as u16 {
0 => " ",
1 => symbols::block::ONE_EIGHTH,
2 => symbols::block::ONE_QUARTER,
3 => symbols::block::THREE_EIGHTHS,
4 => symbols::block::HALF,
5 => symbols::block::FIVE_EIGHTHS,
6 => symbols::block::THREE_QUARTERS,
7 => symbols::block::SEVEN_EIGHTHS,
8 => symbols::block::FULL,
_ => unreachable!("this case should never occur"),
}
}
/// A widget to measure something, using pipe characters ('|') or horizontal bar characters as a unit.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PipeGauge<'a> { pub struct PipeGauge<'a> {
block: Option<Block<'a>>, block: Option<Block<'a>>,
@@ -26,6 +61,7 @@ pub struct PipeGauge<'a> {
label_style: Style, label_style: Style,
gauge_style: Style, gauge_style: Style,
hide_parts: LabelLimit, hide_parts: LabelLimit,
bar_type: BarType,
} }
impl Default for PipeGauge<'_> { impl Default for PipeGauge<'_> {
@@ -38,6 +74,7 @@ impl Default for PipeGauge<'_> {
label_style: Style::default(), label_style: Style::default(),
gauge_style: Style::default(), gauge_style: Style::default(),
hide_parts: LabelLimit::default(), hide_parts: LabelLimit::default(),
bar_type: BarType::default(),
} }
} }
} }
@@ -89,6 +126,12 @@ impl<'a> PipeGauge<'a> {
self.hide_parts = hide_parts; self.hide_parts = hide_parts;
self self
} }
/// What type of bar character to use.
pub fn bar_type(mut self, bar_type: BarType) -> Self {
self.bar_type = bar_type;
self
}
} }
impl Widget for PipeGauge<'_> { impl Widget for PipeGauge<'_> {
@@ -184,11 +227,11 @@ impl Widget for PipeGauge<'_> {
); );
} }
LabelLimit::Auto(_) | LabelLimit::None => { LabelLimit::Auto(_) | LabelLimit::None => {
// FIXME: "[" and "]" don't look that great with block bars.
let (start, _) = buf.set_line(col, row, &Line::from("["), gauge_area.width); let (start, _) = buf.set_line(col, row, &Line::from("["), gauge_area.width);
if start >= gauge_area.right() { if start >= gauge_area.right() {
return; return;
} }
let (end, _) = buf.set_line( let (end, _) = buf.set_line(
(gauge_area.x + gauge_area.width).saturating_sub(1), (gauge_area.x + gauge_area.width).saturating_sub(1),
row, row,
@@ -196,22 +239,40 @@ impl Widget for PipeGauge<'_> {
gauge_area.width, gauge_area.width,
); );
let pipe_end = end.saturating_sub(1).min( let bar_end = end.saturating_sub(1);
start + (f64::from(end.saturating_sub(start)) * self.ratio).floor() as u16, let filled_width = f64::from(bar_end.saturating_sub(start)) * self.ratio;
); let pipe_end = bar_end.min(start + filled_width.floor() as u16);
let symbol = match self.bar_type {
BarType::Pipe => "|",
BarType::Block => symbols::block::FULL,
BarType::Square => "",
};
let bar_style = Style {
fg: self.gauge_style.fg,
bg: None,
add_modifier: self.gauge_style.add_modifier,
sub_modifier: self.gauge_style.sub_modifier,
underline_color: None,
};
for col in start..pipe_end { for col in start..pipe_end {
if let Some(cell) = buf.cell_mut((col, row)) { if let Some(cell) = buf.cell_mut((col, row)) {
cell.set_symbol("|").set_style(Style { cell.set_symbol(symbol).set_style(bar_style);
fg: self.gauge_style.fg,
bg: None,
add_modifier: self.gauge_style.add_modifier,
sub_modifier: self.gauge_style.sub_modifier,
underline_color: None,
});
} }
} }
// Unlike pipes, blocks can also show the leftover fraction of a cell.
// Based on what Ratatui does!
if self.bar_type.is_block()
&& pipe_end < bar_end
&& let Some(cell) = buf.cell_mut((pipe_end, row))
{
cell.set_symbol(get_unicode_block(filled_width.fract()))
.set_style(bar_style);
}
if (end_label.width() as u16) < end.saturating_sub(start) { if (end_label.width() as u16) < end.saturating_sub(start) {
let gauge_end = gauge_area let gauge_end = gauge_area
.right() .right()
@@ -223,3 +284,107 @@ impl Widget for PipeGauge<'_> {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_unicode_block() {
// Test exact values.
assert_eq!(get_unicode_block(0.0), " ");
assert_eq!(get_unicode_block(0.125), symbols::block::ONE_EIGHTH);
assert_eq!(get_unicode_block(0.25), symbols::block::ONE_QUARTER);
assert_eq!(get_unicode_block(0.375), symbols::block::THREE_EIGHTHS);
assert_eq!(get_unicode_block(0.5), symbols::block::HALF);
assert_eq!(get_unicode_block(0.625), symbols::block::FIVE_EIGHTHS);
assert_eq!(get_unicode_block(0.75), symbols::block::THREE_QUARTERS);
assert_eq!(get_unicode_block(0.875), symbols::block::SEVEN_EIGHTHS);
assert_eq!(get_unicode_block(1.0), symbols::block::FULL);
// Test rounding.
assert_eq!(get_unicode_block(0.05), " ");
assert_eq!(get_unicode_block(0.1), symbols::block::ONE_EIGHTH);
assert_eq!(get_unicode_block(0.3), symbols::block::ONE_QUARTER);
assert_eq!(get_unicode_block(0.4), symbols::block::THREE_EIGHTHS);
assert_eq!(get_unicode_block(0.55), symbols::block::HALF);
assert_eq!(get_unicode_block(0.6), symbols::block::FIVE_EIGHTHS);
assert_eq!(get_unicode_block(0.8), symbols::block::THREE_QUARTERS);
assert_eq!(get_unicode_block(0.9), symbols::block::SEVEN_EIGHTHS);
assert_eq!(get_unicode_block(0.99), symbols::block::FULL);
}
/// Create a [`PipeGauge`] and return what it would have rendered.
fn render_gauge(
ratio: f64, bar_type: BarType, start_label: Option<&str>, inner_label: Option<&str>,
) -> String {
const WIDTH: u16 = 12;
let area = Rect::new(0, 0, WIDTH, 1);
let mut buf = Buffer::empty(area);
let mut gauge = PipeGauge::default().ratio(ratio).bar_type(bar_type);
if let Some(start_label) = start_label {
gauge = gauge.start_label(start_label.to_owned());
}
if let Some(inner_label) = inner_label {
gauge = gauge.inner_label(inner_label.to_owned());
}
gauge.render(area, &mut buf);
(0..WIDTH).map(|x| buf[(x, 0)].symbol()).collect()
}
#[test]
fn test_pipe_bars() {
assert_eq!(render_gauge(0.0, BarType::Pipe, None, None), "[ ]");
assert_eq!(render_gauge(0.5, BarType::Pipe, None, None), "[||||| ]");
assert_eq!(
render_gauge(0.95, BarType::Pipe, None, None),
"[||||||||| ]"
);
assert_eq!(render_gauge(1.0, BarType::Pipe, None, None), "[||||||||||]");
}
#[test]
fn test_solid_bars() {
assert_eq!(
render_gauge(0.0, BarType::Block, None, None),
"[ ]"
);
assert_eq!(
render_gauge(0.5, BarType::Block, None, None),
"[█████ ]"
);
assert_eq!(
render_gauge(0.55, BarType::Block, None, None),
"[█████▌ ]"
);
assert_eq!(
render_gauge(0.9, BarType::Block, None, None),
"[█████████ ]"
);
assert_eq!(
render_gauge(1.0, BarType::Block, None, None),
"[██████████]"
);
}
#[test]
fn test_labelled_bars() {
assert_eq!(
render_gauge(0.5, BarType::Pipe, Some("CPU"), Some(" 50%")),
"CPU[||| 50%]"
);
assert_eq!(
render_gauge(0.5, BarType::Block, Some("CPU"), Some(" 50%")),
"CPU[███ 50%]"
);
assert_eq!(
render_gauge(1.0, BarType::Block, Some("CPU"), Some("100%")),
"CPU[███100%]"
);
}
}
+2 -1
View File
@@ -214,7 +214,8 @@ impl Painter {
let block_inner = block.inner(margined_draw_loc); let block_inner = block.inner(margined_draw_loc);
// Draw bar // Draw bar.
// TODO: Switch to pipe_gauge.
f.render_widget( f.render_widget(
Table::new(battery_charge_rows, [Constraint::Percentage(100)]) Table::new(battery_charge_rows, [Constraint::Percentage(100)])
.block(block.clone()) .block(block.clone())
+4 -2
View File
@@ -71,7 +71,8 @@ impl Painter {
.label_style(style) .label_style(style)
.inner_label(inner) .inner_label(inner)
.start_label(outer) .start_label(outer)
.ratio(ratio.into()), .ratio(ratio.into())
.bar_type(self.styles.bar_type),
avg_loc, avg_loc,
); );
avg_row_count += 1; avg_row_count += 1;
@@ -139,7 +140,8 @@ impl Painter {
.inner_label(inner_label) .inner_label(inner_label)
.start_label(start_label) .start_label(start_label)
.ratio(ratio.into()) .ratio(ratio.into())
.hide_parts(hide_parts), .hide_parts(hide_parts)
.bar_type(self.styles.bar_type),
*row, *row,
); );
} }
+10 -5
View File
@@ -81,7 +81,8 @@ impl Painter {
.start_label("RAM") .start_label("RAM")
.inner_label(ram_label) .inner_label(ram_label)
.label_style(self.styles.ram_style) .label_style(self.styles.ram_style)
.gauge_style(self.styles.ram_style), .gauge_style(self.styles.ram_style)
.bar_type(self.styles.bar_type),
); );
if let Some(swap_harvest) = &data.swap_harvest { if let Some(swap_harvest) = &data.swap_harvest {
@@ -94,7 +95,8 @@ impl Painter {
.start_label("SWP") .start_label("SWP")
.inner_label(swap_label) .inner_label(swap_label)
.label_style(self.styles.swap_style) .label_style(self.styles.swap_style)
.gauge_style(self.styles.swap_style), .gauge_style(self.styles.swap_style)
.bar_type(self.styles.bar_type),
); );
} }
@@ -111,7 +113,8 @@ impl Painter {
.start_label("CHE") .start_label("CHE")
.inner_label(cache_fraction_label) .inner_label(cache_fraction_label)
.label_style(self.styles.cache_style) .label_style(self.styles.cache_style)
.gauge_style(self.styles.cache_style), .gauge_style(self.styles.cache_style)
.bar_type(self.styles.bar_type),
); );
} }
} }
@@ -129,7 +132,8 @@ impl Painter {
.start_label("ARC") .start_label("ARC")
.inner_label(arc_fraction_label) .inner_label(arc_fraction_label)
.label_style(self.styles.arc_style) .label_style(self.styles.arc_style)
.gauge_style(self.styles.arc_style), .gauge_style(self.styles.arc_style)
.bar_type(self.styles.bar_type),
); );
} }
} }
@@ -160,7 +164,8 @@ impl Painter {
.start_label("GPU") .start_label("GPU")
.inner_label(label) .inner_label(label)
.label_style(style) .label_style(style)
.gauge_style(style), .gauge_style(style)
.bar_type(self.styles.bar_type),
); );
} }
} }
+4 -2
View File
@@ -723,10 +723,12 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
#text = {colour = "gray"} #text = {colour = "gray"}
#selected_text = {colour = "black", bg_colour = "light blue"} #selected_text = {colour = "black", bg_colour = "light blue"}
#disabled_text = {colour = "dark gray"} #disabled_text = {colour = "dark gray"}
# Disabled by default # Disabled by default.
#bg_colour = "black" #bg_colour = "black"
# Only on Linux # Only supported on Linux.
#thread_text = {colour = "green"} #thread_text = {colour = "green"}
# One of "pipe", "block", or "square".
#bar_type = "pipe"
# 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.
+13 -6
View File
@@ -1,5 +1,6 @@
//! Config options around styling. //! Config options around styling.
mod bars;
mod battery; mod battery;
mod borders; mod borders;
mod cpu; mod cpu;
@@ -29,8 +30,9 @@ use utils::{opt, set_colour, set_colour_list, set_style};
use widgets::WidgetStyle; use widgets::WidgetStyle;
use super::Config; use super::Config;
use crate::options::{ use crate::{
OptionError, OptionResult, args::BottomArgs, config::style::utils::set_bg_colour, canvas::components::pipe_gauge::BarType,
options::{OptionError, OptionResult, args::BottomArgs, config::style::utils::set_bg_colour},
}; };
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@@ -143,6 +145,7 @@ pub struct Styles {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub(crate) thread_text_style: Style, pub(crate) thread_text_style: Style,
pub(crate) border_type: BorderType, pub(crate) border_type: BorderType,
pub(crate) bar_type: BarType,
} }
impl Default for Styles { impl Default for Styles {
@@ -261,10 +264,14 @@ impl Styles {
selected_border_colour selected_border_colour
); );
if let Some(widgets) = &config.widgets if let Some(widgets) = &config.widgets {
&& let Some(widget_borders) = widgets.widget_border_type if let Some(widget_borders) = widgets.widget_border_type {
{ self.border_type = widget_borders.into();
self.border_type = widget_borders.into(); }
if let Some(bar_type) = widgets.bar_type {
self.bar_type = bar_type.into();
}
} }
Ok(()) Ok(())
+75
View File
@@ -0,0 +1,75 @@
use serde::{Deserialize, Serialize};
use crate::canvas::components::pipe_gauge::BarType;
/// The type of character used to fill in bars, such as the ones used by the
/// basic CPU and memory widgets.
#[derive(Default, Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) enum WidgetBarType {
/// Fill bars with pipe characters (`|`).
#[default]
Pipe,
/// Fill bars with block characters (`█`, `▉`, etc.).
Block,
/// Fill bars with square characters (`■`).
Square,
}
impl<'de> Deserialize<'de> for WidgetBarType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?.to_lowercase();
match value.as_str() {
"pipe" => Ok(WidgetBarType::Pipe),
"block" => Ok(WidgetBarType::Block),
"square" => Ok(WidgetBarType::Square),
_ => Err(serde::de::Error::custom("doesn't match any bar type")),
}
}
}
impl From<WidgetBarType> for BarType {
fn from(value: WidgetBarType) -> Self {
match value {
WidgetBarType::Pipe => BarType::Pipe,
WidgetBarType::Block => BarType::Block,
WidgetBarType::Square => BarType::Square,
}
}
}
#[cfg(test)]
mod test {
use super::WidgetBarType;
use crate::options::config::style::widgets::WidgetStyle;
fn parse(value: &str) -> anyhow::Result<Option<WidgetBarType>> {
let style = toml_edit::de::from_str::<WidgetStyle>(&format!("bar_type = {value}"))?;
Ok(style.bar_type)
}
#[test]
fn valid_bar_types() {
assert_eq!(parse("\"pipe\"").unwrap(), Some(WidgetBarType::Pipe));
assert_eq!(parse("\"block\"").unwrap(), Some(WidgetBarType::Block));
assert_eq!(parse("\"square\"").unwrap(), Some(WidgetBarType::Square));
// Casing shouldn't matter.
assert_eq!(parse("\"PIPE\"").unwrap(), Some(WidgetBarType::Pipe));
assert_eq!(parse("\"BLOCK\"").unwrap(), Some(WidgetBarType::Block));
assert_eq!(parse("\"SQUARE\"").unwrap(), Some(WidgetBarType::Square));
}
#[test]
fn invalid_bar_types() {
assert!(parse("\"bar\"").is_err());
assert!(parse("\"\"").is_err());
assert!(parse("true").is_err());
}
}
+1
View File
@@ -2,6 +2,7 @@ use ratatui::widgets::BorderType;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Default, Clone, Copy, Debug, Serialize)] #[derive(Default, Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) enum WidgetBorderType { pub(crate) enum WidgetBorderType {
+2 -1
View File
@@ -4,7 +4,7 @@ use ratatui::{
}; };
use super::colour; use super::colour;
use crate::options::config::style::Styles; use crate::{canvas::components::pipe_gauge::BarType, options::config::style::Styles};
impl Styles { impl Styles {
pub(crate) fn default_palette() -> Self { pub(crate) fn default_palette() -> Self {
@@ -75,6 +75,7 @@ impl Styles {
invalid_query_style: colour!(Color::Red), invalid_query_style: colour!(Color::Red),
disabled_text_style: colour!(Color::DarkGray), disabled_text_style: colour!(Color::DarkGray),
border_type: BorderType::Plain, border_type: BorderType::Plain,
bar_type: BarType::default(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
thread_text_style: colour!(Color::Green), thread_text_style: colour!(Color::Green),
} }
+6 -1
View File
@@ -4,7 +4,10 @@ use ratatui::{
}; };
use super::{colour, hex}; use super::{colour, hex};
use crate::options::config::style::{Styles, themes::hex_colour}; use crate::{
canvas::components::pipe_gauge::BarType,
options::config::style::{Styles, themes::hex_colour},
};
impl Styles { impl Styles {
pub(crate) fn gruvbox_palette() -> Self { pub(crate) fn gruvbox_palette() -> Self {
@@ -75,6 +78,7 @@ impl Styles {
invalid_query_style: colour!(Color::Red), invalid_query_style: colour!(Color::Red),
disabled_text_style: hex!("#665c54"), disabled_text_style: hex!("#665c54"),
border_type: BorderType::Plain, border_type: BorderType::Plain,
bar_type: BarType::default(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
thread_text_style: hex!("#458588"), thread_text_style: hex!("#458588"),
} }
@@ -154,6 +158,7 @@ impl Styles {
invalid_query_style: colour!(Color::Red), invalid_query_style: colour!(Color::Red),
disabled_text_style: hex!("#d5c4a1"), disabled_text_style: hex!("#d5c4a1"),
border_type: BorderType::Plain, border_type: BorderType::Plain,
bar_type: BarType::default(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
thread_text_style: hex!("#458588"), thread_text_style: hex!("#458588"),
} }
+6 -1
View File
@@ -4,7 +4,10 @@ use ratatui::{
}; };
use super::{colour, hex}; use super::{colour, hex};
use crate::options::config::style::{Styles, themes::hex_colour}; use crate::{
canvas::components::pipe_gauge::BarType,
options::config::style::{Styles, themes::hex_colour},
};
impl Styles { impl Styles {
pub(crate) fn nord_palette() -> Self { pub(crate) fn nord_palette() -> Self {
@@ -63,6 +66,7 @@ impl Styles {
invalid_query_style: colour!(Color::Red), invalid_query_style: colour!(Color::Red),
disabled_text_style: hex!("#4c566a"), disabled_text_style: hex!("#4c566a"),
border_type: BorderType::Plain, border_type: BorderType::Plain,
bar_type: BarType::default(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
thread_text_style: hex!("#a3be8c"), thread_text_style: hex!("#a3be8c"),
} }
@@ -124,6 +128,7 @@ impl Styles {
invalid_query_style: colour!(Color::Red), invalid_query_style: colour!(Color::Red),
disabled_text_style: hex!("#d8dee9"), disabled_text_style: hex!("#d8dee9"),
border_type: BorderType::Plain, border_type: BorderType::Plain,
bar_type: BarType::default(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
thread_text_style: hex!("#a3be8c"), thread_text_style: hex!("#a3be8c"),
} }
+5 -1
View File
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{ColourStr, TextStyleConfig, borders::WidgetBorderType}; use super::{ColourStr, TextStyleConfig, bars::WidgetBarType, borders::WidgetBorderType};
/// General styling for generic widgets. /// General styling for generic widgets.
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
@@ -37,4 +37,8 @@ pub(crate) struct WidgetStyle {
/// Background colour for widgets. /// Background colour for widgets.
#[serde(alias = "bg_color")] #[serde(alias = "bg_color")]
pub(crate) bg_colour: Option<ColourStr>, pub(crate) bg_colour: Option<ColourStr>,
/// What character to fill in bars with, such as the ones used by the basic
/// CPU and memory widgets.
pub(crate) bar_type: Option<WidgetBarType>,
} }
+1
View File
@@ -64,3 +64,4 @@ text = { colour = "gray" }
selected_text = { colour = "black", bg_colour = "light blue" } selected_text = { colour = "black", bg_colour = "light blue" }
disabled_text = { colour = "dark gray" } disabled_text = { colour = "dark gray" }
bg_colour = "black" bg_colour = "black"
bar_type = "block"