Skip to content
Merged
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
13 changes: 11 additions & 2 deletions default_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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]
Expand Down
30 changes: 30 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -205,6 +230,9 @@ pub struct DisplayConfig {
#[serde(default)]
pub glyph: GlyphConfig,

#[serde(default)]
pub palette: PaletteConfig,

#[serde(default)]
pub colors: ColorsConfig,

Expand Down Expand Up @@ -240,6 +268,7 @@ fn default_stats() -> Vec<String> {
"disk".to_string(),
"mirror_url".to_string(),
"mirror_health".to_string(),
"colors".to_string(),
]
}

Expand All @@ -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(),
Expand Down
11 changes: 11 additions & 0 deletions src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)),
}
}
Expand Down
145 changes: 110 additions & 35 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<String> {
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();
Expand Down Expand Up @@ -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);
}
}
}
}
}
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -462,8 +534,11 @@ 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!();
Expand Down