From e7d0a2ee6f6343ea5ab10745441ec9362f100445 Mon Sep 17 00:00:00 2001 From: camtisocial Date: Tue, 10 Feb 2026 22:49:22 -0800 Subject: [PATCH 1/2] add color pallete configurations --- default_config.toml | 13 +++- src/config.rs | 30 +++++++++ src/stats.rs | 11 ++++ src/ui/mod.rs | 148 +++++++++++++++++++++++++++++++++----------- 4 files changed, 165 insertions(+), 37 deletions(-) diff --git a/default_config.toml b/default_config.toml index 9d8681a..7f760ff 100644 --- a/default_config.toml +++ b/default_config.toml @@ -5,10 +5,11 @@ ascii = "PACMAN_DEFAULT" ascii_color = "yellow" # Image takes precedence when set -# image = "~/.config/pacfetch/logo.png" +# image = "~/.config/pacfetch/example_image.png" # Available stats: installed, upgradable, last_update, download_size, installed_size, -# net_upgrade_size, orphaned_packages, cache_size, disk, mirror_url, mirror_health +# net_upgrade_size, orphaned_packages, cache_size, disk, mirror_url, mirror_health, +# colors, colors_dark, colors_light stats = [ "title.header", "installed", @@ -22,12 +23,20 @@ stats = [ "cache_size", "mirror_url", "mirror_health", + "colors", ] [display.glyph] glyph = ": " color = "none" +################### PALETTE #################### +# Built-in styles: "blocks" (background-colored), "dots" (● foreground), "ghosts" (󰊠 nerd font) +# Any other value is used as a custom character with foreground color. +[display.palette] +style = "blocks" +spacing = 0 + ################### COLORS #################### # Global stat colors. All support color names, hex (#RRGGBB), or "none". [display.colors] diff --git a/src/config.rs b/src/config.rs index 7ac0895..9cebe59 100644 --- a/src/config.rs +++ b/src/config.rs @@ -80,6 +80,31 @@ impl Default for DiskConfig { } } +#[derive(Deserialize)] +pub struct PaletteConfig { + #[serde(default = "default_palette_style")] + pub style: String, + #[serde(default = "default_palette_spacing")] + pub spacing: usize, +} + +fn default_palette_style() -> String { + "blocks".to_string() +} + +fn default_palette_spacing() -> usize { + 1 +} + +impl Default for PaletteConfig { + fn default() -> Self { + PaletteConfig { + style: default_palette_style(), + spacing: default_palette_spacing(), + } + } +} + #[derive(Deserialize, Default)] pub struct GlyphConfig { #[serde(default = "default_glyph")] @@ -205,6 +230,9 @@ pub struct DisplayConfig { #[serde(default)] pub glyph: GlyphConfig, + #[serde(default)] + pub palette: PaletteConfig, + #[serde(default)] pub colors: ColorsConfig, @@ -240,6 +268,7 @@ fn default_stats() -> Vec { "disk".to_string(), "mirror_url".to_string(), "mirror_health".to_string(), + "colors".to_string(), ] } @@ -251,6 +280,7 @@ impl Default for DisplayConfig { ascii_color: default_ascii_color(), image: String::new(), glyph: GlyphConfig::default(), + palette: PaletteConfig::default(), colors: ColorsConfig::default(), labels: HashMap::new(), title: TitleConfig::default(), diff --git a/src/stats.rs b/src/stats.rs index 9f59225..6b59e2e 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -20,11 +20,19 @@ pub enum StatId { Disk, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PaletteVariant { + Both, + Dark, + Light, +} + #[derive(Debug, Clone, PartialEq)] pub enum StatIdOrTitle { Stat(StatId), NamedTitle(String), LegacyTitle, + ColorPalette(PaletteVariant), } const BYTES_PER_GIB: f64 = 1073741824.0; @@ -55,6 +63,9 @@ impl StatId { "mirror_url" => Ok(StatIdOrTitle::Stat(StatId::MirrorUrl)), "mirror_health" => Ok(StatIdOrTitle::Stat(StatId::MirrorHealth)), "disk" => Ok(StatIdOrTitle::Stat(StatId::Disk)), + "colors" => Ok(StatIdOrTitle::ColorPalette(PaletteVariant::Both)), + "colors_dark" => Ok(StatIdOrTitle::ColorPalette(PaletteVariant::Dark)), + "colors_light" => Ok(StatIdOrTitle::ColorPalette(PaletteVariant::Light)), _ => Err(format!("unknown stat: {}", s)), } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index fd7bdc2..02a16e5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,9 +1,9 @@ mod ascii; use crate::color::parse_color; -use crate::config::{Config, TitleAlign, TitleConfig, TitleStyle, TitleWidth}; +use crate::config::{Config, PaletteConfig, TitleAlign, TitleConfig, TitleStyle, TitleWidth}; use crate::pacman::PacmanStats; -use crate::stats::{StatId, StatIdOrTitle}; +use crate::stats::{PaletteVariant, StatId, StatIdOrTitle}; use crossterm::style::{Color::*, Stylize}; use std::io; @@ -195,6 +195,91 @@ fn resolve_label(stat_id: &StatId, config: &Config) -> String { }) } +const DARK_COLORS: [crossterm::style::Color; 8] = [ + Black, + DarkRed, + DarkGreen, + DarkYellow, + DarkBlue, + DarkMagenta, + DarkCyan, + Grey, +]; +const BRIGHT_COLORS: [crossterm::style::Color; 8] = + [DarkGrey, Red, Green, Yellow, Blue, Magenta, Cyan, White]; + +fn render_palette_row( + colors: &[crossterm::style::Color; 8], + style: &str, + spacing: usize, +) -> String { + let spacer = " ".repeat(spacing); + let mut row = String::new(); + for color in colors { + match style { + "blocks" => row.push_str(&format!("{}{}", " ".on(*color), spacer)), + "dots" => row.push_str(&format!("{}{}", "⬤".with(*color), spacer)), + "ghosts" => row.push_str(&format!("{}{}", "󰊠".with(*color), spacer)), + custom => row.push_str(&format!("{}{}", custom.with(*color), spacer)), + } + } + row +} + +fn palette_row_width(style: &str, spacing: usize) -> usize { + let char_width = match style { + "blocks" => 3, + "dots" | "ghosts" => 1, + custom => custom.chars().count(), + }; + 8 * (char_width + spacing) +} + +fn render_palette_lines( + variant: PaletteVariant, + palette_config: &PaletteConfig, + content_padding: usize, +) -> Vec { + let style = &palette_config.style; + let spacing = palette_config.spacing; + let indent = if content_padding > 0 { + " ".repeat(content_padding) + } else { + String::new() + }; + + let mut lines = Vec::new(); + match variant { + PaletteVariant::Both => { + lines.push(format!( + "{}{}", + indent, + render_palette_row(&DARK_COLORS, style, spacing) + )); + lines.push(format!( + "{}{}", + indent, + render_palette_row(&BRIGHT_COLORS, style, spacing) + )); + } + PaletteVariant::Dark => { + lines.push(format!( + "{}{}", + indent, + render_palette_row(&DARK_COLORS, style, spacing) + )); + } + PaletteVariant::Light => { + lines.push(format!( + "{}{}", + indent, + render_palette_row(&BRIGHT_COLORS, style, spacing) + )); + } + } + lines +} + pub fn display_stats(stats: &PacmanStats, config: &Config) { let glyph = &config.display.glyph.glyph; let parsed_stats = config.display.parsed_stats(); @@ -232,6 +317,12 @@ pub fn display_stats(stats: &PacmanStats, config: &Config) { println!("{}{}{}", label, glyph, value); } } + StatIdOrTitle::ColorPalette(variant) => { + let palette_lines = render_palette_lines(*variant, &config.display.palette, 0); + for line in palette_lines { + println!("{}", line); + } + } } } } @@ -371,6 +462,13 @@ pub fn display_stats_with_graphics(stats: &PacmanStats, config: &Config) -> io:: content_max_width = content_max_width.max(line.chars().count()); stat_lines_raw.push((*stat_id, line)); } + StatIdOrTitle::ColorPalette(_) => { + let row_width = palette_row_width( + &config.display.palette.style, + config.display.palette.spacing, + ); + content_max_width = content_max_width.max(row_width); + } } } @@ -419,40 +517,14 @@ pub fn display_stats_with_graphics(stats: &PacmanStats, config: &Config) -> io:: } } } + StatIdOrTitle::ColorPalette(variant) => { + let palette_lines = + render_palette_lines(*variant, &config.display.palette, content_padding); + stats_lines.extend(palette_lines); + } } } - // Add color palette rows - stats_lines.push(String::new()); - let colors = [ - Black, - DarkRed, - DarkGreen, - DarkYellow, - DarkBlue, - DarkMagenta, - DarkCyan, - Grey, - ]; - let bright_colors = [DarkGrey, Red, Green, Yellow, Blue, Magenta, Cyan, White]; - let mut color_row_1 = String::new(); - for color in &colors { - color_row_1.push_str(&format!("{}", " ".on(*color))); - } - let mut color_row_2 = String::new(); - for color in &bright_colors { - color_row_2.push_str(&format!("{}", " ".on(*color))); - } - // Indent color palette based on content padding - if content_padding > 0 { - let indent = " ".repeat(content_padding); - stats_lines.push(format!("{}{}", indent, color_row_1)); - stats_lines.push(format!("{}{}", indent, color_row_2)); - } else { - stats_lines.push(color_row_1); - stats_lines.push(color_row_2); - } - // Try image rendering first, fall back to ASCII art if !config.display.image.is_empty() { // Pad stats lines with leading spaces to match ASCII art spacing (3-space gap) @@ -462,8 +534,14 @@ pub fn display_stats_with_graphics(stats: &PacmanStats, config: &Config) -> io:: .collect(); if let Some(output) = try_render_with_image(padded_lines, config) { println!(); - // Indent each line for left margin (1 space, matching ASCII path) - for line in output.lines() { + let lines: Vec<&str> = output.lines().collect(); + let is_visible = |l: &&str| !crate::util::strip_ansi(l).trim().is_empty(); + let start = lines.iter().position(is_visible).unwrap_or(0); + let end = lines + .iter() + .rposition(is_visible) + .map_or(0, |i| i + 1); + for line in &lines[start..end] { println!(" {}", line); } println!(); From 1d0cc79f202c57954d501bb14924fd4e9f7ecc91 Mon Sep 17 00:00:00 2001 From: camtisocial Date: Tue, 10 Feb 2026 22:49:50 -0800 Subject: [PATCH 2/2] fmt --- src/ui/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 02a16e5..b4dc938 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -537,10 +537,7 @@ pub fn display_stats_with_graphics(stats: &PacmanStats, config: &Config) -> io:: let lines: Vec<&str> = output.lines().collect(); let is_visible = |l: &&str| !crate::util::strip_ansi(l).trim().is_empty(); let start = lines.iter().position(is_visible).unwrap_or(0); - let end = lines - .iter() - .rposition(is_visible) - .map_or(0, |i| i + 1); + let end = lines.iter().rposition(is_visible).map_or(0, |i| i + 1); for line in &lines[start..end] { println!(" {}", line); }