diff --git a/default_config.toml b/default_config.toml index 0fdb30a..ca3e009 100644 --- a/default_config.toml +++ b/default_config.toml @@ -4,7 +4,6 @@ ascii = "PACMAN_DEFAULT" ascii_color = "yellow" -# Stats to display. Use "title.{name}" to reference titles defined below. # Available stats: installed, upgradable, last_update, download_size, installed_size, # net_upgrade_size, orphaned_packages, cache_size, disk, mirror_url, mirror_health stats = [ @@ -24,6 +23,26 @@ stats = [ [display.glyph] glyph = ": " +color = "none" + +################### COLORS #################### +# Global stat colors. All support color names, hex (#RRGGBB), or "none". +[display.colors] +label = "bright_yellow" +stat = "none" + +# Per-stat color overrides (takes precedence over global) +# [display.colors.installed] +# label = "bright_green" +# stat = "white" + +################### LABELS #################### +# Override label text for individual stats. +# Keys: installed, upgradable, last_update, download_size, installed_size, +# net_upgrade_size, orphaned_packages, cache_size, disk, mirror_url, mirror_health +# [display.labels] +# installed = "Pkgs" +# cache_size = "Cache" ################### TITLES #################### # Define named titles under [display.titles.{name}] @@ -49,25 +68,20 @@ style = "stacked" width = "title" line = "-" -# Example: divider with no text -# [display.titles.divider] -# text = "" -# style = "embedded" -# width = "content" -# line = "─" -# left_cap = "├" -# right_cap = "┤" -# padding = 2 -# Example: footer -# [display.titles.footer] +# example embedded title +# [display.titles.header] +# text = "default" +# text_color = "bright_yellow" +# line_color = "none" # style = "embedded" # width = "content" # line = "─" -# left_cap = "╰" -# right_cap = "╯" +# left_cap = "╭" +# right_cap = "╮" # padding = 2 + ################### DISK #################### [disk] path = "/" diff --git a/src/config.rs b/src/config.rs index ad68f37..1feebce 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,12 +84,44 @@ impl Default for DiskConfig { pub struct GlyphConfig { #[serde(default = "default_glyph")] pub glyph: String, + #[serde(default)] + pub color: String, } fn default_glyph() -> String { ": ".to_string() } +#[derive(Deserialize, Default, Clone)] +pub struct StatColorOverride { + pub label: Option, + pub stat: Option, +} + +#[derive(Deserialize, Clone)] +pub struct ColorsConfig { + #[serde(default = "default_label_color")] + pub label: String, + #[serde(default)] + pub stat: String, + #[serde(flatten)] + pub overrides: HashMap, +} + +fn default_label_color() -> String { + "bright_yellow".to_string() +} + +impl Default for ColorsConfig { + fn default() -> Self { + ColorsConfig { + label: default_label_color(), + stat: String::new(), + overrides: HashMap::new(), + } + } +} + #[derive(Debug, Clone, Deserialize)] pub struct TitleConfig { #[serde(default = "default_title_text")] @@ -170,6 +202,12 @@ pub struct DisplayConfig { #[serde(default)] pub glyph: GlyphConfig, + #[serde(default)] + pub colors: ColorsConfig, + + #[serde(default)] + pub labels: HashMap, + #[serde(default)] pub title: TitleConfig, @@ -209,6 +247,8 @@ impl Default for DisplayConfig { ascii: default_ascii(), ascii_color: default_ascii_color(), glyph: GlyphConfig::default(), + colors: ColorsConfig::default(), + labels: HashMap::new(), title: TitleConfig::default(), titles: HashMap::new(), } diff --git a/src/stats.rs b/src/stats.rs index 0cdba1b..9f59225 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -59,6 +59,23 @@ impl StatId { } } + pub fn config_key(&self) -> &'static str { + match self { + StatId::Title => "title", + StatId::Installed => "installed", + StatId::Upgradable => "upgradable", + StatId::LastUpdate => "last_update", + StatId::DownloadSize => "download_size", + StatId::InstalledSize => "installed_size", + StatId::NetUpgradeSize => "net_upgrade_size", + StatId::OrphanedPackages => "orphaned_packages", + StatId::CacheSize => "cache_size", + StatId::MirrorUrl => "mirror_url", + StatId::MirrorHealth => "mirror_health", + StatId::Disk => "disk", + } + } + pub fn label(&self) -> &'static str { match self { StatId::Title => "", diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 683affa..8a5ef99 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -180,6 +180,21 @@ pub fn resolve_title_text(title_config: &TitleConfig, pacman_version: &Option String { + config + .display + .labels + .get(stat_id.config_key()) + .cloned() + .unwrap_or_else(|| { + if *stat_id == StatId::Disk { + format!("Disk ({})", config.disk.path) + } else { + stat_id.label().to_string() + } + }) +} + pub fn display_stats(stats: &PacmanStats, config: &Config) { let glyph = &config.display.glyph.glyph; let parsed_stats = config.display.parsed_stats(); @@ -213,11 +228,7 @@ pub fn display_stats(stats: &PacmanStats, config: &Config) { } StatIdOrTitle::Stat(stat_id) => { if let Some(value) = stat_id.format_value(stats) { - let label = if *stat_id == StatId::Disk { - format!("Disk ({})", config.disk.path) - } else { - stat_id.label().to_string() - }; + let label = resolve_label(stat_id, config); println!("{}{}{}", label, glyph, value); } } @@ -274,11 +285,7 @@ pub fn display_stats_with_graphics(stats: &PacmanStats, config: &Config) -> io:: let value = stat_id .format_value(stats) .unwrap_or_else(|| "-".to_string()); - let label = if *stat_id == StatId::Disk { - format!("Disk ({})", config.disk.path) - } else { - stat_id.label().to_string() - }; + let label = resolve_label(stat_id, config); let line = format!("{}{}{}", label, glyph, value); content_max_width = content_max_width.max(line.chars().count()); stat_lines_raw.push((*stat_id, line)); @@ -400,38 +407,57 @@ fn format_stat_with_colors( config: &Config, glyph: &str, ) -> String { + let label = resolve_label(&stat_id, config); + let colors = &config.display.colors; + let key = stat_id.config_key(); + + // Resolve label color: per-stat override > global + let label_color_str = colors + .overrides + .get(key) + .and_then(|o| o.label.as_deref()) + .unwrap_or(&colors.label); + let label_color = parse_color(label_color_str); + let colored_label = match label_color { + Some(c) => format!("{}", label.bold().with(c)), + None => format!("{}", label.bold()), + }; + + // glyph color + let glyph_color = parse_color(&config.display.glyph.color); + let colored_glyph = match glyph_color { + Some(c) => format!("{}", glyph.with(c)), + None => glyph.to_string(), + }; + if stat_id == StatId::MirrorHealth { - match (&stats.mirror_url, stats.mirror_sync_age_hours) { - (Some(_), Some(age)) => { - let label = StatId::MirrorHealth.label(); - format!( - "{}{}{} (last sync {:.1} hours)", - label.bold().with(Yellow), - glyph, - "OK".green(), - age - ) - } - (Some(_), None) => { - let label = StatId::MirrorHealth.label(); - format!( - "{}{}{} - could not check sync status", - label.bold().with(Yellow), - glyph, - "Err".red() - ) - } - (None, _) => { - let label = StatId::MirrorHealth.label(); - format!( - "{}{}{} - no mirror found", - label.bold().with(Yellow), - glyph, - "Err".red() - ) - } - } + let val_override = colors + .overrides + .get(key) + .and_then(|o| o.stat.as_deref()) + .and_then(parse_color); + let value_str = match (&stats.mirror_url, stats.mirror_sync_age_hours) { + (Some(_), Some(age)) => match val_override { + Some(c) => format!("{} (last sync {:.1} hours)", "OK".with(c), age), + None => format!("{} (last sync {:.1} hours)", "OK".green(), age), + }, + (Some(_), None) => match val_override { + Some(c) => format!("{} - could not check sync status", "Err".with(c)), + None => format!("{} - could not check sync status", "Err".red()), + }, + (None, _) => match val_override { + Some(c) => format!("{} - no mirror found", "Err".with(c)), + None => format!("{} - no mirror found", "Err".red()), + }, + }; + format!("{}{}{}", colored_label, colored_glyph, value_str) } else if stat_id == StatId::Disk { + // Per-stat value override replaces semantic percentage colors + let val_override = colors + .overrides + .get(key) + .and_then(|o| o.stat.as_deref()) + .and_then(parse_color); if let (Some(used), Some(total)) = (stats.disk_used_bytes, stats.disk_total_bytes) { let used_gib = used as f64 / 1073741824.0; let total_gib = total as f64 / 1073741824.0; @@ -441,31 +467,33 @@ fn format_stat_with_colors( 0.0 }; let pct_str = format!("({:.0}%)", pct); - let colored_pct = if pct > 90.0 { - format!("{}", pct_str.red()) - } else if pct >= 70.0 { - format!("{}", pct_str.yellow()) - } else { - format!("{}", pct_str.green()) + let colored_pct = match val_override { + Some(c) => format!("{}", pct_str.with(c)), + None if pct > 90.0 => format!("{}", pct_str.red()), + None if pct >= 70.0 => format!("{}", pct_str.yellow()), + None => format!("{}", pct_str.green()), }; - let label = format!("Disk ({})", config.disk.path); format!( "{}{}{:.2} GiB / {:.2} GiB {}", - label.bold().with(Yellow), - glyph, - used_gib, - total_gib, - colored_pct + colored_label, colored_glyph, used_gib, total_gib, colored_pct ) } else { - let label = format!("Disk ({})", config.disk.path); - format!("{}{}-", label.bold().with(Yellow), glyph) + format!("{}{}-", colored_label, colored_glyph) } } else { - let label = stat_id.label(); let value = stat_id .format_value(stats) .unwrap_or_else(|| "-".to_string()); - format!("{}{}{}", label.bold().with(Yellow), glyph, value) + let value_color_str = colors + .overrides + .get(key) + .and_then(|o| o.stat.as_deref()) + .unwrap_or(&colors.stat); + let value_color = parse_color(value_color_str); + let colored_value = match value_color { + Some(c) => format!("{}", value.with(c)), + None => value, + }; + format!("{}{}{}", colored_label, colored_glyph, colored_value) } }