diff --git a/src/choose.rs b/src/choose.rs new file mode 100644 index 0000000..7f01b52 --- /dev/null +++ b/src/choose.rs @@ -0,0 +1,61 @@ +use rand::prelude::IteratorRandom; +use rand::Rng; + +pub trait Options { + fn all() -> Vec where Self: Sized; +} + +pub struct Chooser { + rng: TRng +} + +impl Chooser { + pub fn new(rng: TRng) -> Self { + Self { rng } + } + + pub fn choose(&mut self, selection: Vec) -> TValue { + let options = if selection.is_empty() { + TValue::all() + } else { + selection + }; + options.into_iter().choose_stable(&mut self.rng).unwrap() + } +} + +#[cfg(test)] +mod test { + use rand::rngs::mock::StepRng; + use crate::{Chooser, Options}; + + enum MockOptions { + First, + Second, + Third + } + + impl Options for MockOptions { + fn all() -> Vec where Self: Sized { + use MockOptions::*; + + vec![First, Second, Third] + } + } + + #[test] + fn choose() { + let rng = StepRng::new(0, 1); + let mut chooser = Chooser::new(rng); + + assert!(matches!(chooser.choose(vec![MockOptions::First, MockOptions::Second]), MockOptions::Second)); + } + + #[test] + fn choose_empty() { + let rng = StepRng::new(0, 1); + let mut chooser = Chooser::new(rng); + + assert!(matches!(chooser.choose(Vec::new()), MockOptions::Third)); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3a92010..d7b4eaa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,11 @@ use std::time::Duration; use anyhow::Error; use clap::Parser; use clap::ArgEnum; -use crossterm::style::Color::*; +use rand::rngs::OsRng; use crate::animation::Animation; use crate::animation::circle::CircleAnimation; use crate::char::SimpleCharSampler; +use crate::choose::{Chooser, Options}; use crate::color::{ColorSampler, SimpleColorSampler}; use crate::fill::circle::CircleFillMode; use crate::fill::FillMode; @@ -29,12 +30,21 @@ mod sampler; mod render; mod timer; mod runner; +mod choose; #[derive(Copy, Clone, ArgEnum)] enum AnimationType { Circle } +impl Options for AnimationType { + fn all() -> Vec where Self: Sized { + use AnimationType::*; + + vec![Circle] + } +} + #[derive(Copy, Clone, ArgEnum)] enum ColorType { Red, @@ -47,12 +57,28 @@ enum ColorType { Rainbow, } +impl Options for ColorType { + fn all() -> Vec where Self: Sized { + use ColorType::*; + + vec![Red, Green, Blue, LightRed, LightGreen, LightBlue, Grey, Rainbow] + } +} + #[derive(Copy, Clone, ArgEnum)] enum FillModeType { Circle, Level } +impl Options for FillModeType { + fn all() -> Vec where Self: Sized { + use FillModeType::*; + + vec![Circle, Level] + } +} + #[derive(Parser)] #[clap(author = env!("CARGO_PKG_AUTHORS"), version = env!("CARGO_PKG_VERSION"), about = env!("CARGO_PKG_DESCRIPTION"))] struct Args { @@ -64,6 +90,10 @@ struct Args { color: Vec, #[clap(long, default_value = ".-+%#", help = "Set chars")] chars: String, + #[clap(long, default_value = "30", help = "Set frames per second")] + fps: u64, + #[clap(long, default_value = "1000", help = "Set duration [milliseconds]")] + duration: u64, #[clap(long, help = "Set width [default: terminal width]")] width: Option, #[clap(long, help = "Set height [default: terminal height]")] @@ -72,23 +102,26 @@ struct Args { fn main() -> Result<(), Error> { let args = Args::parse(); + let mut chooser = Chooser::new(OsRng::default()); let terminal = crossterm::terminal::size()?; let width = args.width.unwrap_or(terminal.0 as usize); - let height = args.width.unwrap_or(terminal.1 as usize); + let height = args.height.unwrap_or(terminal.1 as usize); let size = Vector::from_terminal(width, height); + let delay = Duration::from_micros(1_000_000 / args.fps); + let duration = Duration::from_millis(args.duration); - let animation = create_animation(args.animation[0], size); - let fill = create_fill(args.fill[0], size); - let color = create_color(args.color[0]); + let animation = create_animation(chooser.choose(args.animation), size); + let fill = create_fill(chooser.choose(args.fill), size); + let color = create_color(chooser.choose(args.color)); let char = Box::new(SimpleCharSampler::new(args.chars)); let sampler = ComposedSampler::new(animation, fill, color, char); - let surface =WriteSurface::new(stdout(), width, height); + let surface = WriteSurface::new(stdout(), width, height); let renderer = SamplerRenderer::new(surface, sampler); - let timer = SimpleTimer::new(Duration::from_millis(1000 / 60)); - let runner = Runner::new(Duration::from_secs(2), timer, renderer); + let timer = SimpleTimer::new(delay); + let runner = Runner::new(duration, timer, renderer); runner.run() } @@ -107,6 +140,8 @@ fn create_fill(fill: FillModeType, size: Vector) -> Box { } fn create_color(color: ColorType) -> Box { + use crossterm::style::Color::*; + match color { ColorType::Red => Box::new(SimpleColorSampler::new(vec![Yellow, DarkYellow, Red])), ColorType::Green => Box::new(SimpleColorSampler::new(vec![Cyan, DarkGreen, Green])),