From 50c6a925a88eeab005b7f42ae50860cd448076db Mon Sep 17 00:00:00 2001 From: kendy-lovely <166834945+kendy-lovely@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:07:12 +0700 Subject: [PATCH] new palette enum to accomodate different palette types --- crates/hyfetch/src/bin/hyfetch.rs | 167 ++++++++++++++++++++++------ crates/hyfetch/src/cli_options.rs | 13 ++- crates/hyfetch/src/color_util.rs | 7 ++ crates/hyfetch/src/models.rs | 54 ++++++++- crates/hyfetch/src/neofetch_util.rs | 14 ++- 5 files changed, 214 insertions(+), 41 deletions(-) diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index 36254196..bec394bc 100644 --- a/crates/hyfetch/src/bin/hyfetch.rs +++ b/crates/hyfetch/src/bin/hyfetch.rs @@ -8,6 +8,7 @@ use std::iter; use std::iter::zip; use std::num::NonZeroU8; use std::path::{Path, PathBuf}; +use std::str::FromStr; use aho_corasick::AhoCorasick; use anyhow::{Context as _, Result}; @@ -24,7 +25,7 @@ use hyfetch::color_util::{ NeofetchAsciiIndexedColor, PresetIndexedColor, Theme as _, ToAnsiString as _, }; use hyfetch::distros::Distro; -use hyfetch::models::{build_hex_color_profile, Config, PresetValue}; +use hyfetch::models::{Config, Palette, PresetValue, build_hex_color_profile}; #[cfg(feature = "macchina")] use hyfetch::neofetch_util::macchina_path; use hyfetch::neofetch_util::{self, add_pkg_path, fastfetch_path, get_distro_ascii, get_distro_name, literal_input, ColorAlignment, NEOFETCH_COLORS_AC, NEOFETCH_COLOR_PATTERNS, TEST_ASCII}; @@ -137,8 +138,20 @@ fn main() -> Result<()> { let args = options.args.as_ref().or(config.args.as_ref()); #[cfg(feature = "macchina")] let palette_glyph: Option<&String> = options.palette_glyph.as_ref().or(config.palette_glyph.as_ref()); + #[cfg(feature = "macchina")] + let palette_type: Option<&String> = options.palette_type.as_ref().or(config.palette_glyph.as_ref()); + #[cfg(feature = "macchina")] + let palette: Option = if let Some(glyph) = palette_glyph { + if let Some(type_str) = palette_type { + if let Ok(mut parsed_type) = Palette::from_str(type_str) { + parsed_type.get_glyph_mut().push_str(glyph); + Some(parsed_type) + } else { Some(Palette::Full(glyph.to_owned())) } + } else { Some(Palette::Full(glyph.to_owned())) } + } else { None }; + #[cfg(not(feature = "macchina"))] - let palette_glyph: Option<&String> = None; + let palette: Option<&String> = None; fn parse_preset_string(preset_string: &str, config: &Config) -> Result { if preset_string.contains('#') { @@ -229,7 +242,7 @@ fn main() -> Result<()> { let asc = asc .to_recolored(&color_align, &color_profile, color_mode, theme) .context("failed to recolor ascii")?; - neofetch_util::run(asc, backend, args, palette_glyph)?; + neofetch_util::run(asc, backend, args, palette)?; if options.ask_exit { @@ -1239,44 +1252,54 @@ fn create_config( ////////////////////////////// // 7.5. If using macchina, ask for custom palette glyph - #[cfg(feature = "macchina")] - let mut palette_glyph: Option = None; - + let mut palette: Option = Some(Palette::Full("".to_owned())); + #[cfg(feature = "macchina")] if backend == Backend::Macchina { + use hyfetch::formatc; + + let mut current_title = "Create a glyph for macchina (leave empty for None):"; + clear_screen(Some(&title), color_mode, debug_mode) .context("failed to clear screen")?; - print_title_prompt(option_counter, "Create a glyph for macchina (leave empty for None):"); + print_title_prompt(option_counter, current_title); - let color_palette: [&str; 8] = match theme { - TerminalTheme::Dark => ["&0","&4","&2","&6","&1","&5","&3","&8"], - TerminalTheme::Light => ["&8", "&c", "&a", "&e", "&9", "&d", "&b", "&f"] - }; + const DARK_COLORS: [&str; 8] = ["&0","&4","&2","&6","&1","&5","&3","&8"]; + const LIGHT_COLORS: [&str; 8] = ["&8","&c","&a","&e","&9","&d","&b","&f"]; - let print_palette = |glyph: &str, palette_list: [&str; 8]| -> Result<()> { - clear_screen(Some(&title), color_mode, debug_mode) - .context("failed to clear screen")?; - print_title_prompt(option_counter, "Create a glyph for macchina (leave empty for None):"); - - let palette_for_print: String = palette_list + let to_palette = |glyph: &str, colors: [&str; 8]| -> String { + colors .into_iter() .map(|s| s.to_owned() + glyph + "&r" ) - .collect(); + .collect() + }; - printc!("Preview:\n{}", palette_for_print); - print!("> {glyph}"); + let print_palette = |palette: &mut Palette, current_title: &str| -> Result<()> { + clear_screen(Some(&title), color_mode, debug_mode) + .context("failed to clear screen")?; + print_title_prompt(option_counter, current_title); + + match palette { + Palette::Full(glyph) => + printc!("Preview:\n{}\n{}\n", to_palette(glyph, DARK_COLORS), + to_palette(glyph, LIGHT_COLORS)), + Palette::Light(glyph) => + printc!("Preview:\n{}\n\n", to_palette(glyph, LIGHT_COLORS)), + Palette::Dark(glyph) => + printc!("Preview:\n{}\n\n", to_palette(glyph, DARK_COLORS)) + } + print!("> {}", palette.get_glyph()); io::stdout().flush().context("failed to flush preset prompt")?; Ok(()) }; let mut raw_mode = RawModeGuard::new().context("failed to initialize raw input mode")?; - let mut entered_glyph = String::new(); loop { raw_mode .disable() .context("failed to disable raw mode for rendering")?; - print_palette(&entered_glyph, color_palette).context("failed to clear screen during glyph input")?; + print_palette(palette.as_mut().unwrap(), current_title).context("failed to print palette during glyph input")?; raw_mode .enable() @@ -1294,10 +1317,18 @@ fn create_config( break; }, KeyCode::Backspace => { - entered_glyph.pop(); + palette + .as_mut() + .unwrap() + .get_glyph_mut() + .pop(); }, KeyCode::Esc => { - entered_glyph.clear(); + palette + .as_mut() + .unwrap() + .get_glyph_mut() + .clear(); }, KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { raw_mode @@ -1310,27 +1341,97 @@ fn create_config( if !key.modifiers.contains(KeyModifiers::CONTROL) && !key.modifiers.contains(KeyModifiers::ALT) => { - entered_glyph.push(c); + palette + .as_mut() + .unwrap() + .get_glyph_mut() + .push(c); }, _ => {}, } } raw_mode .disable() - .context("failed to disable raw mode after preset selection")?; + .context("failed to disable raw mode after entering glyph")?; + drop(raw_mode); + + if palette.as_ref().unwrap().get_glyph().is_empty() { + palette = None; + } - palette_glyph = if !entered_glyph.is_empty() { - Some(entered_glyph) - } else { None }; + if let Some(palette) = palette.as_mut() { + current_title = "Pick your palette type!"; + + let mut raw_mode = RawModeGuard::new().context("failed to initialize raw input mode")?; + let mut select_display: String; + loop { + raw_mode + .disable() + .context("failed to disable raw mode for rendering")?; + + print_palette(palette, current_title).context("failed to clear screen during glyph input")?; + select_display = match palette { + Palette::Full(_) => formatc!("&l&o&nFull&r, Light, Dark"), + Palette::Light(_) => formatc!("Full, &l&o&nLight&r, Dark"), + Palette::Dark(_) => formatc!("Full, Light, &l&o&nDark") + }; + println!("\n> {}", select_display); + + raw_mode + .enable() + .context("failed to enable raw mode for key input")?; + let event = event::read().context("failed to read keyboard event")?; + let Event::Key(key) = event else { + continue; + }; + if !matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) { + continue; + } + match key.code { + KeyCode::Enter => break, + KeyCode::Left => palette.shift_type(false), + KeyCode::Right => palette.shift_type(true), + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + raw_mode + .disable() + .context("failed to disable raw mode before interrupting")?; + println!(); + return Err(anyhow::anyhow!("interrupted by user")); + }, + _ => {} + } + } + }; + + let preview: &str = match palette.as_ref() { + Some(p) => match p { + Palette::Full(glyph) => + &formatc!("\n{}\n{}", to_palette(glyph, DARK_COLORS), + to_palette(glyph, LIGHT_COLORS)), + Palette::Light(glyph) => + &formatc!("\n{}", to_palette(glyph, LIGHT_COLORS)), + Palette::Dark(glyph) => + &formatc!("\n{}", to_palette(glyph, DARK_COLORS)) + }, + None => "None" + }; update_title( &mut title, &mut option_counter, - "Selected glyph", - &palette_glyph.as_ref().unwrap_or(&String::from("None")), + "Customized palette", + preview ); } + let palette_glyph: Option = if let Some(palette) = palette.as_ref() { + Some(palette.get_glyph().to_owned()) + } else { None }; + let palette_type: Option = if let Some(palette) = palette.as_ref() { + Some(palette.to_string()) + } else { None }; + + ////////////////////////////// // 8. Custom ASCII file let mut custom_ascii_path: Option = None; @@ -1392,8 +1493,8 @@ fn create_config( pride_month_disable: false, custom_ascii_path, custom_presets: None, - #[cfg(feature = "macchina")] - palette_glyph + palette_glyph, + palette_type }; debug!(?config, "created config"); diff --git a/crates/hyfetch/src/cli_options.rs b/crates/hyfetch/src/cli_options.rs index 3e8fbbbd..acc1acb0 100644 --- a/crates/hyfetch/src/cli_options.rs +++ b/crates/hyfetch/src/cli_options.rs @@ -33,7 +33,9 @@ pub struct Options { pub ask_exit: bool, pub auto_detect_light_dark: Option, #[cfg(feature = "macchina")] - pub palette_glyph: Option + pub palette_glyph: Option, + #[cfg(feature = "macchina")] + pub palette_type: Option } pub fn options() -> OptionParser { @@ -160,6 +162,12 @@ BACKEND={{{backends}}}", .help("Sets the glyph to be used for the macchina backend") .argument("STR") .optional(); + #[cfg(feature = "macchina")] + let palette_type = long("palette-type") + .help("Sets the type of palette to be used for the macchina backend") + .argument("full,light,dark") + .optional(); + #[cfg(feature = "macchina")] #[cfg(feature = "macchina")] return construct!(Options { @@ -180,7 +188,8 @@ BACKEND={{{backends}}}", test_print, ask_exit, auto_detect_light_dark, - palette_glyph + palette_glyph, + palette_type }) .to_options() .header( diff --git a/crates/hyfetch/src/color_util.rs b/crates/hyfetch/src/color_util.rs index 6cabdc0a..d558ddeb 100644 --- a/crates/hyfetch/src/color_util.rs +++ b/crates/hyfetch/src/color_util.rs @@ -410,6 +410,13 @@ macro_rules! printc { }; } +#[macro_export] +macro_rules! formatc { + ($($arg:tt)*) => { + format!("{}", color(format!("{}&r", format!($($arg)*)), AnsiMode::Rgb).expect("failed to color message")); + }; +} + /// Prints with color. pub fn printc(msg: S, mode: AnsiMode) -> Result<()> where diff --git a/crates/hyfetch/src/models.rs b/crates/hyfetch/src/models.rs index e01f899a..e432f480 100644 --- a/crates/hyfetch/src/models.rs +++ b/crates/hyfetch/src/models.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use anyhow::{Context as _, Result}; use serde::{Deserialize, Serialize}; +use strum::{AsRefStr, Display, EnumString}; use crate::color_profile::ColorProfile; use crate::color_util::Lightness; @@ -25,7 +26,9 @@ pub struct Config { pub custom_ascii_path: Option, pub custom_presets: Option>>, #[cfg(feature = "macchina")] - pub palette_glyph: Option + pub palette_glyph: Option, + #[cfg(feature = "macchina")] + pub palette_type: Option } impl Config { @@ -188,3 +191,52 @@ impl PresetValue { } } } + +// handling macchina palettes +#[derive(Clone, Debug, Display, EnumString, Deserialize, Serialize, AsRefStr)] +pub enum Palette { + #[strum(to_string = "Full", ascii_case_insensitive)] + Full(String), + #[strum(to_string = "Light", ascii_case_insensitive)] + Light(String), + #[strum(to_string = "Dark", ascii_case_insensitive)] + Dark(String) +} + +impl Palette { + pub fn get_glyph(&self) -> &String { + match self { + Palette::Full(s) => s, + Palette::Light(s) => s, + Palette::Dark(s) => s + } + } + pub fn get_glyph_mut(&mut self) -> &mut String { + match self { + Palette::Full(s) => s, + Palette::Light(s) => s, + Palette::Dark(s) => s + } + } + pub fn shift_type(&mut self, go_right: bool) { + *self = if go_right { + match self { + Palette::Full(s) => Palette::Light(s.to_owned()), + Palette::Light(s) => Palette::Dark(s.to_owned()), + Palette::Dark(s) => Palette::Full(s.to_owned()) + } + } else { + match self { + Palette::Full(s) => Palette::Dark(s.to_owned()), + Palette::Light(s) => Palette::Full(s.to_owned()), + Palette::Dark(s) => Palette::Light(s.to_owned()) + } + } + } +} + +#[test] +fn test() { + println!("{:?}", Palette::Full("".to_owned())) +} + diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs index 3b6546ff..edfbcce0 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -13,6 +13,9 @@ use indexmap::IndexMap; use itertools::Itertools as _; #[cfg(windows)] use anyhow::anyhow; +#[cfg(feature = "macchina")] +use crate::models::Palette; +#[cfg(feature = "macchina")] #[cfg(windows)] use crate::utils::find_file; #[cfg(windows)] @@ -275,14 +278,14 @@ where } #[tracing::instrument(level = "debug", skip(asc), fields(asc.w = asc.w, asc.h = asc.h))] -pub fn run(asc: RecoloredAsciiArt, backend: Backend, args: Option<&Vec>, palette_glyph: Option<&String>) -> Result<()> { +pub fn run(asc: RecoloredAsciiArt, backend: Backend, args: Option<&Vec>, palette: Option) -> Result<()> { let asc = asc.lines.join("\n"); match backend { Backend::Neofetch => run_neofetch(asc, args).context("failed to run neofetch")?, Backend::Fastfetch => run_fastfetch(asc, args).context("failed to run fastfetch")?, #[cfg(feature = "macchina")] - Backend::Macchina => run_macchina(asc, args, palette_glyph).context("failed to run macchina")?, + Backend::Macchina => run_macchina(asc, args, palette).context("failed to run macchina")?, } Ok(()) @@ -666,7 +669,7 @@ fn run_fastfetch(asc: String, args: Option<&Vec>) -> Result<()> { /// Runs macchina with custom ascii art. #[cfg(feature = "macchina")] #[tracing::instrument(level = "debug", skip(asc))] -fn run_macchina(asc: String, args: Option<&Vec>, palette_glyph: Option<&String>) -> Result<()> { +fn run_macchina(asc: String, args: Option<&Vec>, palette: Option) -> Result<()> { // Write ascii art to temp file let asc_file_path = { let mut temp_file = tempfile::Builder::new() @@ -697,9 +700,10 @@ fn run_macchina(asc: String, args: Option<&Vec>, palette_glyph: Option<& "path", &*asc_file_path.to_string_lossy(), )])); - if let Some(palette_glyph) = palette_glyph { + if let Some(p) = palette { doc["palette"] = Item::Table(Table::from_iter([ - ("glyph", value(palette_glyph)), + ("type", value(p.to_string())), + ("glyph", value(p.get_glyph())), ("visible", value(true)) ])) }