diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index ae37410a..56f9401d 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}; @@ -135,6 +136,22 @@ fn main() -> Result<()> { let backend = options.backend.unwrap_or(config.backend); 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_type.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: Option<&String> = None; fn parse_preset_string(preset_string: &str, config: &Config) -> Result { if preset_string.contains('#') { @@ -225,7 +242,8 @@ 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)?; + neofetch_util::run(asc, backend, args, palette)?; + if options.ask_exit { input(Some("Press enter to exit...")).context("failed to read input")?; @@ -1231,6 +1249,189 @@ fn create_config( backend.as_ref(), ); + ////////////////////////////// + // 7.5. If using macchina, ask for custom palette glyph + + 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, current_title); + + 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 to_palette = |glyph: &str, colors: [&str; 8]| -> String { + colors + .into_iter() + .map(|s| s.to_owned() + glyph + "&r" ) + .collect() + }; + + 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")?; + loop { + raw_mode + .disable() + .context("failed to disable raw mode for rendering")?; + + print_palette(palette.as_mut().unwrap(), current_title).context("failed to print palette during glyph input")?; + + 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::Backspace => { + palette + .as_mut() + .unwrap() + .get_glyph_mut() + .pop(); + }, + KeyCode::Esc => { + palette + .as_mut() + .unwrap() + .get_glyph_mut() + .clear(); + }, + 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")); + }, + KeyCode::Char(c) + if !key.modifiers.contains(KeyModifiers::CONTROL) + && !key.modifiers.contains(KeyModifiers::ALT) => + { + palette + .as_mut() + .unwrap() + .get_glyph_mut() + .push(c); + }, + _ => {}, + } + } + raw_mode + .disable() + .context("failed to disable raw mode after entering glyph")?; + drop(raw_mode); + + if palette.as_ref().unwrap().get_glyph().is_empty() { + palette = 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, + "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; @@ -1292,6 +1493,8 @@ fn create_config( pride_month_disable: false, custom_ascii_path, custom_presets: None, + 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 494a770d..bf250b7d 100644 --- a/crates/hyfetch/src/cli_options.rs +++ b/crates/hyfetch/src/cli_options.rs @@ -32,6 +32,10 @@ pub struct Options { pub test_print: bool, pub ask_exit: bool, pub auto_detect_light_dark: Option, + #[cfg(feature = "macchina")] + pub palette_glyph: Option, + #[cfg(feature = "macchina")] + pub palette_type: Option } pub fn options() -> OptionParser { @@ -153,7 +157,19 @@ BACKEND={{{backends}}}", .argument("BOOL") .optional(); - construct!(Options { + #[cfg(feature = "macchina")] + let palette_glyph = long("palette-glyph") + .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")] + return construct!(Options { config, config_file, preset, @@ -171,6 +187,8 @@ BACKEND={{{backends}}}", test_print, ask_exit, auto_detect_light_dark, + palette_glyph, + palette_type }) .to_options() .header( @@ -180,7 +198,37 @@ BACKEND={{{backends}}}", ) .expect("header should not contain invalid color codes"), ) - .version(env!("CARGO_PKG_VERSION")) + .version(env!("CARGO_PKG_VERSION")); + + #[cfg(not(feature = "macchina"))] + return construct!(Options { + config, + config_file, + preset, + mode, + backend, + args, + scale, + lightness, + june, + debug, + distro, + ascii_file, + print_font_logo, + // hidden + test_print, + ask_exit, + auto_detect_light_dark + }) + .to_options() + .header( + &*color( + "&l&bhyfetch&~&L - neofetch with flags <3", + AnsiMode::Ansi256, + ) + .expect("header should not contain invalid color codes"), + ) + .version(env!("CARGO_PKG_VERSION")); } #[cfg(feature = "autocomplete")] 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 10672bb8..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; @@ -24,6 +25,10 @@ pub struct Config { pub pride_month_disable: bool, pub custom_ascii_path: Option, pub custom_presets: Option>>, + #[cfg(feature = "macchina")] + pub palette_glyph: Option, + #[cfg(feature = "macchina")] + pub palette_type: Option } impl Config { @@ -186,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 47869321..edfbcce0 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; use std::ffi::OsStr; -#[cfg(feature = "macchina")] use std::fs; use std::io::{Write as _}; use std::path::PathBuf; @@ -14,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)] @@ -33,7 +35,10 @@ use crate::ascii::{RawAsciiArt, RecoloredAsciiArt}; use crate::color_util::{printc, NeofetchAsciiIndexedColor, PresetIndexedColor}; use crate::distros::Distro; use crate::types::{AnsiMode, Backend}; +#[cfg(feature = "macchina")] use crate::utils::{find_in_path, get_cache_path, input, process_command_status}; +#[cfg(not(feature = "macchina"))] +use crate::utils::{get_cache_path, input, process_command_status}; pub const TEST_ASCII: &str = r####################" ### |\___/| ### @@ -273,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>) -> 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).context("failed to run macchina")?, + Backend::Macchina => run_macchina(asc, args, palette).context("failed to run macchina")?, } Ok(()) @@ -664,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>) -> 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() @@ -695,6 +700,13 @@ fn run_macchina(asc: String, args: Option<&Vec>) -> Result<()> { "path", &*asc_file_path.to_string_lossy(), )])); + if let Some(p) = palette { + doc["palette"] = Item::Table(Table::from_iter([ + ("type", value(p.to_string())), + ("glyph", value(p.get_glyph())), + ("visible", value(true)) + ])) + } doc }; debug!(%theme_doc, "macchina theme");