pub mod convert; pub mod pattern; pub mod transform; mod error; mod exec; mod printer; mod renderer; mod term; mod vec; pub use error::*; pub use exec::*; pub use printer::*; pub use renderer::*; pub use term::*; pub use vec::*; use crate::convert::*; use crate::pattern::*; use crate::transform::*; use cancellation::CancellationTokenSource; use clap::builder::NonEmptyStringValueParser; use clap::{value_parser, Parser, ValueEnum}; use crossterm::style::Color; use crossterm::style::Color::*; use rand::prelude::*; use std::io::stdout; use std::time::Duration; /// The command line arguments. #[derive(Parser, Default)] #[command( author = env!("CARGO_PKG_AUTHORS"), version = env!("CARGO_PKG_VERSION"), about = env!("CARGO_PKG_DESCRIPTION"), )] struct Args { /// Set the animation duration as milliseconds #[arg( long, default_value_t = 2000, value_parser = value_parser!(u64).range(0..=60_000), help = "Set the animation duration [milliseconds]" )] duration: u64, /// Set the frames per second #[arg(long, default_value_t = 60, value_parser = value_parser!(u64).range(1..=480))] fps: u64, /// Choose the chars used to draw the pattern #[arg(long, default_value = ".:+#", value_parser = NonEmptyStringValueParser::new())] chars: String, /// Choose the pattern #[arg(long, value_enum)] char_pattern: Option, /// Choose whether to invert the pattern #[arg(long)] char_invert: Option, /// Choose whether to swap the x-axis and y-axis of the pattern #[arg(long)] char_swap: Option, /// Choose the segment count of the pattern [default: 1-4] #[arg(long, value_parser = value_parser!(u8).range(1..255))] char_segments: Option, /// Choose the factor by which to shrink the pattern [default: 1-4] #[arg(long, value_parser = value_parser!(u8).range(1..255))] char_shrink: Option, /// Choose the colors used for the pattern #[arg(long, value_enum)] colors: Option, /// Choose the fill pattern #[arg(long, value_enum)] color_pattern: Option, /// Choose whether the fill pattern should move #[arg(long)] color_shift: Option, /// Choose whether to invert the fill pattern #[arg(long)] color_invert: Option, /// Choose whether to swap the x-axis and y-axis of the fill pattern #[arg(long)] color_swap: Option, /// Choose the segment count of the fill pattern [default: 1-4] #[arg(long, value_parser = value_parser!(u8).range(1..255))] color_segments: Option, } /// All color pallets. #[derive(ValueEnum, Copy, Clone)] enum PalletEnum { Red, Yellow, Green, Blue, Magenta, Cyan, Rainbow, DarkRed, DarkYellow, DarkGreen, DarkBlue, DarkMagenta, DarkCyan, DarkRainbow, RedYellow, YellowGreen, GreenBlue, BlueCyan, CyanMagenta, MagentaRed, Gray, } /// All possible [Pattern]s. #[derive(ValueEnum, Copy, Clone, PartialEq, Debug)] enum PatternEnum { Circle, Line, Rhombus, Wheel, } /// A configuration for a composed [Pattern]. #[derive(derive_more::Constructor)] struct PatternConfig { pattern: PatternEnum, shift: bool, invert: bool, swap: bool, segments: f32, shrink: f32, } impl Args { /// Returns the configuration for the char [Pattern]. fn char_config(&self, rng: &mut impl Rng) -> PatternConfig { PatternConfig::new( choose(self.char_pattern, rng), true, self.char_invert.unwrap_or(rng.gen()), self.char_swap.unwrap_or(rng.gen()), self.char_segments.unwrap_or(rng.gen_range(1..=4)) as f32, self.char_shrink.unwrap_or(rng.gen_range(1..=4)) as f32, ) } /// Returns the configuration for the color [Pattern]. fn color_config(&self, rng: &mut impl Rng) -> PatternConfig { PatternConfig::new( choose(self.color_pattern, rng), self.color_shift.unwrap_or(rng.gen()), self.color_invert.unwrap_or(rng.gen()), self.color_swap.unwrap_or(rng.gen()), self.color_segments.unwrap_or(rng.gen_range(1..=4)) as f32, 1.0, ) } /// Returns the colors for the [ColorConverter]. fn pallet(&self, rng: &mut impl Rng) -> Vec { match choose(self.colors, rng) { PalletEnum::Red => vec![DarkRed, Red, White], PalletEnum::Yellow => vec![DarkYellow, Yellow, White], PalletEnum::Green => vec![DarkGreen, Green, White], PalletEnum::Blue => vec![DarkBlue, Blue, White], PalletEnum::Magenta => vec![DarkMagenta, Magenta, White], PalletEnum::Cyan => vec![DarkCyan, Cyan, White], PalletEnum::Rainbow => vec![Red, Yellow, Green, Blue, Cyan, Magenta], PalletEnum::DarkRed => vec![Black, DarkRed, Red], PalletEnum::DarkYellow => vec![Black, DarkYellow, Yellow], PalletEnum::DarkGreen => vec![Black, DarkGreen, Green], PalletEnum::DarkBlue => vec![Black, DarkBlue, Blue], PalletEnum::DarkMagenta => vec![Black, DarkMagenta, Magenta], PalletEnum::DarkCyan => vec![Black, DarkCyan, Cyan], PalletEnum::DarkRainbow => vec![ DarkRed, DarkYellow, DarkGreen, DarkBlue, DarkCyan, DarkMagenta, ], PalletEnum::RedYellow => vec![Red, DarkRed, DarkYellow, Yellow], PalletEnum::YellowGreen => vec![Yellow, DarkYellow, DarkGreen, Green], PalletEnum::GreenBlue => vec![Green, DarkGreen, DarkBlue, Blue], PalletEnum::BlueCyan => vec![Blue, DarkBlue, DarkCyan, Cyan], PalletEnum::CyanMagenta => vec![Cyan, DarkCyan, DarkMagenta, Magenta], PalletEnum::MagentaRed => vec![Magenta, DarkMagenta, DarkRed, Red], PalletEnum::Gray => vec![Black, DarkGrey, Grey, White], } } /// Returns the duration for the [Timer]. fn duration(&self) -> Duration { Duration::from_millis(self.duration) } /// Returns the delay for the [Timer]. fn delay(&self) -> Duration { Duration::from_nanos(1_000_000_000 / self.fps) } } impl PatternConfig { /// Creates a new base [Pattern]. fn create_base(&self) -> Box { match self.pattern { PatternEnum::Circle => Box::new(CircleFactory::new()), PatternEnum::Line => Box::new(LineFactory::new()), PatternEnum::Rhombus => Box::new(RhombusFactory::new()), PatternEnum::Wheel => Box::new(WheelFactory::new()), } } /// Creates a new composed [Pattern]. fn create(&self) -> Box { let mut pattern = self.create_base(); if self.shift { pattern = Box::new(ShiftFactory::new(pattern)) } if self.invert { pattern = Box::new(InvertFactory::new(pattern)) } if self.swap { pattern = Box::new(SwapFactory::new(pattern)) } if self.segments != 1.0 { pattern = Box::new(SegmentsFactory::new(pattern, self.segments)); } if self.shrink != 1.0 { pattern = Box::new(ShrinkFactory::new(pattern, self.shrink)); } pattern } } /// Returns the value of the [Option] or a random enum variant. fn choose(opt: Option, rng: &mut TRand) -> TValue { match opt { Some(value) => value.clone(), None => TValue::value_variants().iter().choose(rng).unwrap().clone(), } } fn main() -> Result<(), Error> { let args = Args::parse(); let rand = &mut thread_rng(); let char = args.char_config(rand).create(); let color = args.color_config(rand).create(); let pallet = args.pallet(rand); let duration = args.duration(); let delay = args.delay(); let sampler = SamplerFactoryImpl::new(char, color); let char_converter = CharConverterImpl::new(args.chars); let color_converter = ColorConverterImpl::new(pallet); let converter = ConverterImpl::new(char_converter, color_converter); let term = TerminalImpl::new(stdout()); let printer = PrinterImpl::new(term)?; let renderer = RendererImpl::new(sampler, converter, printer)?; let clock = ClockImpl::new(); let executor = Executor::new(clock, duration, delay); let src = CancellationTokenSource::new(); let token = src.token().clone(); ctrlc::set_handler(move || { src.cancel(); })?; executor.run(renderer, &token) } #[cfg(test)] mod test { use super::*; use approx::*; use rand::rngs::mock::StepRng; #[test] fn args_pallet_all_defined() { let rand = &mut StepRng::new(1, 1); for value in PalletEnum::value_variants() { let args = Args { colors: Some(*value), ..Args::default() }; assert!(args.pallet(rand).len() > 0); } } #[test] fn duration() { let args = Args { duration: 3500, ..Args::default() }; assert_eq!(Duration::from_millis(3500), args.duration()); } #[test] fn delay() { let args = Args { fps: 20, ..Args::default() }; assert_eq!(Duration::from_millis(50), args.delay()); } #[test] fn char_config_pattern() { let rng = &mut StepRng::new(1, 1); let args = Args { char_pattern: Some(PatternEnum::Line), ..Args::default() }; assert_eq!(PatternEnum::Line, args.char_config(rng).pattern); } #[test] fn char_config_invert() { let rng = &mut StepRng::new(1, 1); let args = Args { char_invert: Some(false), ..Args::default() }; assert_eq!(false, args.char_config(rng).invert); } #[test] fn char_config_shift() { let rng = &mut StepRng::new(1, 1); let args = Args::default(); assert_eq!(true, args.char_config(rng).shift); } #[test] fn char_config_swap() { let rng = &mut StepRng::new(1, 1); let args = Args { char_swap: Some(true), ..Args::default() }; assert_eq!(true, args.char_config(rng).swap); } #[test] fn char_config_segments() { let rng = &mut StepRng::new(1, 1); let args = Args { char_segments: Some(12), ..Args::default() }; assert_abs_diff_eq!(12.0, args.char_config(rng).segments); } #[test] fn char_config_shrink() { let rng = &mut StepRng::new(1, 1); let args = Args { char_shrink: Some(42), ..Args::default() }; assert_abs_diff_eq!(42.0, args.char_config(rng).shrink); } #[test] fn color_config_pattern() { let rng = &mut StepRng::new(1, 1); let args = Args { color_pattern: Some(PatternEnum::Circle), ..Args::default() }; assert_eq!(PatternEnum::Circle, args.color_config(rng).pattern); } #[test] fn color_config_invert() { let rng = &mut StepRng::new(1, 1); let args = Args { color_invert: Some(true), ..Args::default() }; assert_eq!(true, args.color_config(rng).invert); } #[test] fn color_config_shift() { let rng = &mut StepRng::new(1, 1); let args = Args { color_shift: Some(false), ..Args::default() }; assert_eq!(false, args.color_config(rng).shift); } #[test] fn color_config_swap() { let rng = &mut StepRng::new(1, 1); let args = Args { color_swap: Some(true), ..Args::default() }; assert_eq!(true, args.color_config(rng).swap); } #[test] fn color_config_segments() { let rng = &mut StepRng::new(1, 1); let args = Args { color_segments: Some(23), ..Args::default() }; assert_abs_diff_eq!(23.0, args.color_config(rng).segments); } #[test] fn color_config_shrink() { let rng = &mut StepRng::new(1, 1); let args = Args::default(); assert_abs_diff_eq!(1.0, args.color_config(rng).shrink); } #[test] fn pattern_config_all_defined() { for value in PatternEnum::value_variants() { let config = PatternConfig { pattern: *value, shift: true, invert: true, swap: true, segments: 3.0, shrink: 2.0, }; config .create() .create(&Config::default()) .sample(Vector::default()); } } }