Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/configuration/config-file/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,4 @@ These can be set under `[styles.widgets]`:
| `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 }` |
| `thread_text` | Text styling for text when representing process threads. Only usable on Linux at the moment. | `thread_text = { color = "green", bg_color = "blue", bold = true }` |
| `progress_bar_chars` | Characters to use for progress bars | `progress_bar_chars = ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]` |
99 changes: 91 additions & 8 deletions src/canvas/components/pipe_gauge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub enum LabelLimit {
#[derive(Debug, Clone)]
pub struct PipeGauge<'a> {
block: Option<Block<'a>>,
/// Characters to use for the progress bar
progress_chars: &'a [char],
ratio: f64,
start_label: Option<Line<'a>>,
inner_label: Option<Line<'a>>,
Expand All @@ -28,8 +30,8 @@ pub struct PipeGauge<'a> {
hide_parts: LabelLimit,
}

impl Default for PipeGauge<'_> {
fn default() -> Self {
impl<'a> PipeGauge<'a> {
pub fn new(progress_chars: &'a [char]) -> Self {
Self {
block: None,
ratio: 0.0,
Expand All @@ -38,11 +40,10 @@ impl Default for PipeGauge<'_> {
label_style: Style::default(),
gauge_style: Style::default(),
hide_parts: LabelLimit::default(),
progress_chars,
}
}
}

impl<'a> PipeGauge<'a> {
/// The ratio, a value from 0.0 to 1.0 (any other greater or less will be
/// clamped) represents the portion of the pipe gauge to fill.
///
Expand Down Expand Up @@ -196,11 +197,9 @@ impl Widget for PipeGauge<'_> {
gauge_area.width,
);

let pipe_end =
start + (f64::from(end.saturating_sub(start)) * self.ratio).floor() as u16;
for col in start..pipe_end {
for (char, col) in progress_bar(self.progress_chars, start, end, self.ratio) {
if let Some(cell) = buf.cell_mut((col, row)) {
cell.set_symbol("|").set_style(Style {
cell.set_char(char).set_style(Style {
fg: self.gauge_style.fg,
bg: None,
add_modifier: self.gauge_style.add_modifier,
Expand All @@ -221,3 +220,87 @@ impl Widget for PipeGauge<'_> {
}
}
}

/// Returns an iterator over characters of the progress bar, and their positions
///
/// # Arguments
///
/// - `chars`: The characters to use for the progress bar
/// - `bar_start`: Start position
/// - `bar_end`: End position
/// - `ratio`: How full the progress bar is
///
/// # Panics
///
/// `chars` must be non-empty
fn progress_bar(
chars: &[char], bar_start: u16, bar_end: u16, ratio: f64,
) -> impl Iterator<Item = (char, u16)> {
let bar_len = f64::from(bar_end.saturating_sub(bar_start)) * ratio;

// Length of the bar, without accounting for the partial final character
let bar_len_truncated = bar_len.floor();

// The final progress character to display.
// This might be `None` if we can't display even the minimum segment, in
// which case we won't display anything at all.
//
// This might happen when, for example, we have 5 progress characters: [1, 2, 3, 4, .],
// 10 cells, and our progress is 50.1%. We will display 5 full cells:
//
// 50.1%: .....
//
// If it was 50.2% progress, we would display 5 full cells, and 1 cell with the first character:
//
// 50.2%: .....1
// ^ extra
let final_progress_char = {
// The ratio of a single progress bar character that we lost due to truncation
//
// This ratio will be displayed as a "partial" character
let final_char_ratio = (bar_len - bar_len_truncated).clamp(0.0, 1.0);

let char_index = (final_char_ratio * chars.len() as f64).floor() as usize;

// -1 because 0-based indexing
char_index.checked_sub(1).and_then(|it| chars.get(it))
};

let bar_end = bar_start + bar_len_truncated as u16;

(bar_start..bar_end)
.map(move |pos| (*chars.last().expect("chars is non-empty"), pos))
.chain(final_progress_char.map(|ch| (*ch, bar_end)))
}

#[cfg(test)]
mod tests {
#[test]
fn progress_bar() {
let bars = (0..11)
.map(|i| {
let fill = i as f64 * 0.1;
super::progress_bar(&['1', '2', '3', '4', '.'], 0, 2, fill)
.map(|(ch, _)| ch)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();

assert_eq!(
bars,
vec![
vec![],
vec!['1'],
vec!['2'],
vec!['3'],
vec!['4'],
vec!['.'],
vec!['.', '1'],
vec!['.', '2'],
vec!['.', '3'],
vec!['.', '4'],
vec!['.', '.']
]
);
}
}
4 changes: 2 additions & 2 deletions src/canvas/widgets/cpu_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl Painter {
avg_loc.width -= 2;

f.render_widget(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.gauge_style(style)
.label_style(style)
.inner_label(inner)
Expand Down Expand Up @@ -128,7 +128,7 @@ impl Painter {

for ((start_label, inner_label, ratio, style), row) in chunk.zip(rows.iter()) {
f.render_widget(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.gauge_style(style)
.label_style(style)
.inner_label(inner_label)
Expand Down
10 changes: 5 additions & 5 deletions src/canvas/widgets/mem_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl Painter {
};

draw_widgets.push(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.ratio(ram_percentage / 100.0)
.start_label("RAM")
.inner_label(ram_label)
Expand All @@ -86,7 +86,7 @@ impl Painter {
let swap_label = memory_label(swap_harvest, app_state.basic_mode_use_percent);

draw_widgets.push(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.ratio(swap_percentage / 100.0)
.start_label("SWP")
.inner_label(swap_label)
Expand All @@ -103,7 +103,7 @@ impl Painter {
memory_label(cache_harvest, app_state.basic_mode_use_percent);

draw_widgets.push(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.ratio(cache_percentage / 100.0)
.start_label("CHE")
.inner_label(cache_fraction_label)
Expand All @@ -121,7 +121,7 @@ impl Painter {
memory_label(arc_harvest, app_state.basic_mode_use_percent);

draw_widgets.push(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.ratio(arc_percentage / 100.0)
.start_label("ARC")
.inner_label(arc_fraction_label)
Expand Down Expand Up @@ -152,7 +152,7 @@ impl Painter {
};

draw_widgets.push(
PipeGauge::default()
PipeGauge::new(&self.styles.progress_bar_chars.0)
.ratio(percentage / 100.0)
.start_label("GPU")
.inner_label(label)
Expand Down
5 changes: 4 additions & 1 deletion src/options/config/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ use utils::{opt, set_colour, set_colour_list, set_style};
use widgets::WidgetStyle;

use super::Config;
use crate::options::{OptionError, OptionResult, args::BottomArgs};
use crate::options::{
OptionError, OptionResult, args::BottomArgs, config::style::widgets::ProgressBarChars,
};

#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
Expand Down Expand Up @@ -127,6 +129,7 @@ pub struct Styles {
#[cfg(target_os = "linux")]
pub(crate) thread_text_style: Style,
pub(crate) border_type: BorderType,
pub(crate) progress_bar_chars: ProgressBarChars,
}

impl Default for Styles {
Expand Down
3 changes: 2 additions & 1 deletion src/options/config/style/themes/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tui::{
};

use super::color;
use crate::options::config::style::Styles;
use crate::options::config::style::{Styles, widgets::ProgressBarChars};

impl Styles {
pub(crate) fn default_palette() -> Self {
Expand Down Expand Up @@ -69,6 +69,7 @@ impl Styles {
border_type: BorderType::Plain,
#[cfg(target_os = "linux")]
thread_text_style: color!(Color::Green),
progress_bar_chars: ProgressBarChars::default(),
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/options/config/style/themes/gruvbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tui::{
};

use super::{color, hex};
use crate::options::config::style::{Styles, themes::hex_colour};
use crate::options::config::style::{Styles, themes::hex_colour, widgets::ProgressBarChars};

impl Styles {
pub(crate) fn gruvbox_palette() -> Self {
Expand Down Expand Up @@ -69,6 +69,7 @@ impl Styles {
border_type: BorderType::Plain,
#[cfg(target_os = "linux")]
thread_text_style: hex!("#458588"),
progress_bar_chars: ProgressBarChars::default(),
}
}

Expand Down Expand Up @@ -134,6 +135,7 @@ impl Styles {
border_type: BorderType::Plain,
#[cfg(target_os = "linux")]
thread_text_style: hex!("#458588"),
progress_bar_chars: ProgressBarChars::default(),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/options/config/style/themes/nord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tui::{
};

use super::{color, hex};
use crate::options::config::style::{Styles, themes::hex_colour};
use crate::options::config::style::{Styles, themes::hex_colour, widgets::ProgressBarChars};

impl Styles {
pub(crate) fn nord_palette() -> Self {
Expand Down Expand Up @@ -57,6 +57,7 @@ impl Styles {
border_type: BorderType::Plain,
#[cfg(target_os = "linux")]
thread_text_style: hex!("#a3be8c"),
progress_bar_chars: ProgressBarChars::default(),
}
}

Expand Down Expand Up @@ -110,6 +111,7 @@ impl Styles {
border_type: BorderType::Plain,
#[cfg(target_os = "linux")]
thread_text_style: hex!("#a3be8c"),
progress_bar_chars: ProgressBarChars::default(),
}
}
}
Expand Down
55 changes: 55 additions & 0 deletions src/options/config/style/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,59 @@ pub(crate) struct WidgetStyle {

/// Widget borders type.
pub(crate) widget_border_type: Option<WidgetBorderType>,

/// Progress bar characters to use
pub(crate) progress_bar_chars: Option<ProgressBarChars>,
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) struct ProgressBarChars(pub(crate) Vec<char>);

impl<'de> Deserialize<'de> for ProgressBarChars {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Vec::<char>::deserialize(deserializer).and_then(|chars| {
if chars.is_empty() {
Err(<D::Error as serde::de::Error>::custom(
"the array of progress bar characters must be non-empty",
))
} else {
Ok(Self(chars))
}
})
}
}

impl Serialize for ProgressBarChars {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}

#[cfg(feature = "generate_schema")]
impl schemars::JsonSchema for ProgressBarChars {
fn schema_name() -> std::borrow::Cow<'static, str> {
Vec::<char>::schema_name()
}

fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = generator.subschema_for::<Vec<char>>();
schema.insert(
"minItems".into(),
serde_json::Value::Number(serde_json::Number::from(1u64)),
);
schema
}
}

impl Default for ProgressBarChars {
fn default() -> Self {
Self(vec!['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'])
}
}
2 changes: 2 additions & 0 deletions tests/invalid_configs/empty_progress_bar_chars.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[styles.widgets]
progress_bar_chars = []
2 changes: 2 additions & 0 deletions tests/valid_configs/progress_bar_chars.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[styles.widgets]
progress_bar_chars = ["x"]