use std::cell::Cell;
use serde::{Deserialize, Serialize};
use crate::ported::action::{
Htop_Reaction, State, HTOP_KEEP_FOLLOWING, HTOP_REDRAW_BAR, HTOP_REFRESH,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum BarStyle {
#[default]
Classic,
Gradient,
Solid,
Thin,
Ascii,
}
impl BarStyle {
fn next(self) -> BarStyle {
match self {
BarStyle::Classic => BarStyle::Gradient,
BarStyle::Gradient => BarStyle::Solid,
BarStyle::Solid => BarStyle::Thin,
BarStyle::Thin => BarStyle::Ascii,
BarStyle::Ascii => BarStyle::Classic,
}
}
pub fn label(self) -> &'static str {
match self {
BarStyle::Classic => "classic",
BarStyle::Gradient => "gradient",
BarStyle::Solid => "solid",
BarStyle::Thin => "thin",
BarStyle::Ascii => "ascii",
}
}
}
thread_local! {
static CURRENT: Cell<BarStyle> = const { Cell::new(BarStyle::Classic) };
}
pub fn current() -> BarStyle {
CURRENT.with(Cell::get)
}
pub fn set(style: BarStyle) {
CURRENT.with(|c| c.set(style));
}
pub fn fill_glyph(cell: i32, total_filled: i32, bar_w: i32) -> Option<char> {
let is_tip = cell == total_filled - 1;
let frac = if bar_w > 0 {
cell as f64 / bar_w as f64
} else {
0.0
};
match current() {
BarStyle::Classic => None,
BarStyle::Solid => Some('\u{2588}'), BarStyle::Thin => Some(if is_tip { '\u{25B8}' } else { '\u{25AC}' }), BarStyle::Ascii => Some(if is_tip { '>' } else { '#' }),
BarStyle::Gradient => Some(if is_tip {
'\u{25B8}' } else if frac < 0.33 {
'\u{2588}' } else if frac < 0.55 {
'\u{2593}' } else if frac < 0.80 {
'\u{2592}' } else {
'\u{2591}' }),
}
}
pub fn cycle_bar_style(_st: &mut State) -> Htop_Reaction {
let next = current().next();
set(next);
crate::extensions::prefs::update(|p| p.bar_style = next);
crate::extensions::overlay::set_status(format!("Bar style: {}", next.label()));
HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING
}
pub fn init_from_prefs() {
if let Some(p) = crate::extensions::prefs::load() {
set(p.bar_style);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cycle_order_wraps_through_all_five() {
let seq = [
BarStyle::Classic,
BarStyle::Gradient,
BarStyle::Solid,
BarStyle::Thin,
BarStyle::Ascii,
];
let mut s = BarStyle::Classic;
for expected_next in seq
.iter()
.skip(1)
.chain(std::iter::once(&BarStyle::Classic))
{
s = s.next();
assert_eq!(s, *expected_next);
}
}
#[test]
fn classic_is_none() {
set(BarStyle::Classic);
assert_eq!(fill_glyph(0, 5, 10), None);
assert_eq!(fill_glyph(4, 5, 10), None);
}
#[test]
fn styles_render_body_and_tip() {
set(BarStyle::Solid);
assert_eq!(fill_glyph(0, 5, 10), Some('\u{2588}'));
assert_eq!(fill_glyph(4, 5, 10), Some('\u{2588}'));
set(BarStyle::Thin);
assert_eq!(fill_glyph(0, 5, 10), Some('\u{25AC}'));
assert_eq!(fill_glyph(4, 5, 10), Some('\u{25B8}'));
set(BarStyle::Ascii);
assert_eq!(fill_glyph(0, 5, 10), Some('#'));
assert_eq!(fill_glyph(4, 5, 10), Some('>'));
set(BarStyle::Classic); }
#[test]
fn gradient_shades_by_position() {
set(BarStyle::Gradient);
assert_eq!(fill_glyph(10, 60, 100), Some('\u{2588}')); assert_eq!(fill_glyph(40, 60, 100), Some('\u{2593}')); assert_eq!(fill_glyph(60, 80, 100), Some('\u{2592}')); assert_eq!(fill_glyph(90, 100, 100), Some('\u{2591}')); assert_eq!(fill_glyph(59, 60, 100), Some('\u{25B8}')); set(BarStyle::Classic);
}
#[test]
fn zero_width_is_safe() {
set(BarStyle::Gradient);
assert_eq!(fill_glyph(0, 0, 0), Some('\u{2588}')); set(BarStyle::Classic);
}
}