2022-04-09 11:01:36 +00:00
|
|
|
use std::io::stdout;
|
2022-04-09 15:05:43 +00:00
|
|
|
use std::time::Duration;
|
2022-04-11 19:02:11 +00:00
|
|
|
use anyhow::{anyhow, Error};
|
2022-04-02 15:19:48 +00:00
|
|
|
use clap::Parser;
|
2022-04-09 10:56:57 +00:00
|
|
|
use clap::ArgEnum;
|
2022-04-09 16:25:45 +00:00
|
|
|
use rand::rngs::OsRng;
|
2022-04-09 10:56:57 +00:00
|
|
|
use crate::animation::Animation;
|
|
|
|
use crate::animation::circle::CircleAnimation;
|
2022-04-09 17:29:30 +00:00
|
|
|
use crate::animation::rhombus::RhombusAnimation;
|
2022-04-15 05:58:08 +00:00
|
|
|
use crate::animation::sonar::SonarAnimation;
|
2022-04-09 11:01:36 +00:00
|
|
|
use crate::char::SimpleCharSampler;
|
2022-04-13 17:41:59 +00:00
|
|
|
use crate::choose::{Chooser, Collection};
|
2022-04-09 10:56:57 +00:00
|
|
|
use crate::color::{ColorSampler, SimpleColorSampler};
|
|
|
|
use crate::fill::circle::CircleFillMode;
|
|
|
|
use crate::fill::FillMode;
|
|
|
|
use crate::fill::level::LevelFillMode;
|
2022-04-09 17:29:30 +00:00
|
|
|
use crate::fill::stripes::StripesFillMode;
|
2022-04-09 14:07:49 +00:00
|
|
|
use crate::render::{Renderer, SamplerRenderer};
|
2022-04-09 15:05:43 +00:00
|
|
|
use crate::runner::Runner;
|
2022-04-09 11:01:36 +00:00
|
|
|
use crate::sampler::ComposedSampler;
|
|
|
|
use crate::surface::WriteSurface;
|
2022-04-09 15:05:43 +00:00
|
|
|
use crate::timer::SimpleTimer;
|
2022-04-09 10:56:57 +00:00
|
|
|
use crate::vec::Vector;
|
2022-04-02 15:19:48 +00:00
|
|
|
|
2022-04-03 12:44:26 +00:00
|
|
|
mod color;
|
2022-04-02 15:20:52 +00:00
|
|
|
mod char;
|
2022-04-02 15:25:35 +00:00
|
|
|
mod fill;
|
|
|
|
mod vec;
|
2022-04-03 14:03:28 +00:00
|
|
|
mod array;
|
2022-04-06 16:39:20 +00:00
|
|
|
mod surface;
|
2022-04-08 16:59:52 +00:00
|
|
|
mod animation;
|
2022-04-08 18:05:00 +00:00
|
|
|
mod sampler;
|
2022-04-09 14:07:49 +00:00
|
|
|
mod render;
|
2022-04-09 15:05:43 +00:00
|
|
|
mod timer;
|
|
|
|
mod runner;
|
2022-04-09 16:25:45 +00:00
|
|
|
mod choose;
|
2022-04-02 15:19:48 +00:00
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// Defines an enum and implements the [Collection] trait.
|
2022-04-09 17:37:57 +00:00
|
|
|
macro_rules! options {
|
|
|
|
($name:ident { $($opt:ident,)* }) => {
|
|
|
|
#[derive(Copy, Clone, ArgEnum)]
|
|
|
|
enum $name {
|
|
|
|
$($opt,)*
|
|
|
|
}
|
2022-04-13 17:41:59 +00:00
|
|
|
impl Collection for $name {
|
2022-04-09 17:37:57 +00:00
|
|
|
fn all() -> Vec<Self> {
|
|
|
|
vec![$($name::$opt,)*]
|
|
|
|
}
|
|
|
|
}
|
2022-04-09 16:25:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-09 17:37:57 +00:00
|
|
|
options!(AnimationType {
|
|
|
|
Circle,
|
|
|
|
Rhombus,
|
2022-04-15 05:58:08 +00:00
|
|
|
Sonar,
|
2022-04-09 17:37:57 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
options!(ColorType {
|
2022-04-09 10:56:57 +00:00
|
|
|
Red,
|
|
|
|
Green,
|
|
|
|
Blue,
|
|
|
|
LightRed,
|
|
|
|
LightGreen,
|
|
|
|
LightBlue,
|
|
|
|
Grey,
|
|
|
|
Rainbow,
|
2022-04-09 17:37:57 +00:00
|
|
|
});
|
2022-04-09 10:56:57 +00:00
|
|
|
|
2022-04-09 17:37:57 +00:00
|
|
|
options!(FillModeType {
|
2022-04-09 10:56:57 +00:00
|
|
|
Circle,
|
2022-04-09 17:29:30 +00:00
|
|
|
Level,
|
2022-04-09 17:37:57 +00:00
|
|
|
Stripes,
|
|
|
|
});
|
2022-04-09 16:25:45 +00:00
|
|
|
|
2022-04-11 19:02:11 +00:00
|
|
|
const MAX_FPS: u64 = 480;
|
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// The program arguments.
|
2022-04-02 15:19:48 +00:00
|
|
|
#[derive(Parser)]
|
2022-04-09 17:37:57 +00:00
|
|
|
#[clap(author = env ! ("CARGO_PKG_AUTHORS"), version = env ! ("CARGO_PKG_VERSION"), about = env ! ("CARGO_PKG_DESCRIPTION"))]
|
2022-04-02 15:19:48 +00:00
|
|
|
struct Args {
|
2022-04-09 10:56:57 +00:00
|
|
|
#[clap(short, long, help = "Add animation", arg_enum)]
|
|
|
|
animation: Vec<AnimationType>,
|
|
|
|
#[clap(short, long, help = "Add fill mode", arg_enum)]
|
|
|
|
fill: Vec<FillModeType>,
|
|
|
|
#[clap(short, long, help = "Add color pallet", arg_enum)]
|
|
|
|
color: Vec<ColorType>,
|
2022-04-11 19:02:11 +00:00
|
|
|
#[clap(long, default_value = ".-+%#", parse(try_from_str = validate_chars), help = "Set chars")]
|
2022-04-09 10:56:57 +00:00
|
|
|
chars: String,
|
2022-04-11 19:02:11 +00:00
|
|
|
#[clap(long, default_value = "30", parse(try_from_str = validate_fps), help = "Set frames per second [max: 480]")]
|
2022-04-09 16:25:45 +00:00
|
|
|
fps: u64,
|
2022-04-11 19:02:11 +00:00
|
|
|
#[clap(long, default_value = "1000", parse(try_from_str = validate_duration), help = "Set duration [milliseconds]")]
|
2022-04-09 16:25:45 +00:00
|
|
|
duration: u64,
|
2022-04-09 10:56:57 +00:00
|
|
|
#[clap(long, help = "Set width [default: terminal width]")]
|
|
|
|
width: Option<usize>,
|
|
|
|
#[clap(long, help = "Set height [default: terminal height]")]
|
|
|
|
height: Option<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<(), Error> {
|
|
|
|
let args = Args::parse();
|
2022-04-09 16:25:45 +00:00
|
|
|
let mut chooser = Chooser::new(OsRng::default());
|
2022-04-09 10:56:57 +00:00
|
|
|
|
2022-04-11 19:02:11 +00:00
|
|
|
let (width, height) = size(crossterm::terminal::size()?, args.width, args.height);
|
2022-04-09 10:56:57 +00:00
|
|
|
let size = Vector::from_terminal(width, height);
|
2022-04-11 19:02:11 +00:00
|
|
|
let delay = delay_of_fps(args.fps);
|
2022-04-09 16:25:45 +00:00
|
|
|
let duration = Duration::from_millis(args.duration);
|
2022-04-09 10:56:57 +00:00
|
|
|
|
2022-04-09 16:25:45 +00:00
|
|
|
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));
|
2022-04-09 11:01:36 +00:00
|
|
|
let char = Box::new(SimpleCharSampler::new(args.chars));
|
|
|
|
|
2022-04-09 15:12:34 +00:00
|
|
|
let sampler = ComposedSampler::new(animation, fill, color, char);
|
2022-04-09 16:25:45 +00:00
|
|
|
let surface = WriteSurface::new(stdout(), width, height);
|
2022-04-09 14:07:49 +00:00
|
|
|
|
2022-04-09 15:12:34 +00:00
|
|
|
let renderer = SamplerRenderer::new(surface, sampler);
|
2022-04-09 16:25:45 +00:00
|
|
|
let timer = SimpleTimer::new(delay);
|
|
|
|
let runner = Runner::new(duration, timer, renderer);
|
2022-04-09 14:07:49 +00:00
|
|
|
|
2022-04-09 15:05:43 +00:00
|
|
|
runner.run()
|
2022-04-02 15:19:48 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// Validates the chars argument.
|
2022-04-11 19:02:11 +00:00
|
|
|
fn validate_chars(text: &str) -> Result<String, Error> {
|
|
|
|
if text.is_empty() {
|
|
|
|
Err(anyhow!("can't be empty."))
|
|
|
|
} else {
|
|
|
|
Ok(text.to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// Validates the fps argument.
|
2022-04-11 19:02:11 +00:00
|
|
|
fn validate_fps(text: &str) -> Result<u64, Error> {
|
|
|
|
let value = text.parse()?;
|
|
|
|
|
|
|
|
if value > MAX_FPS {
|
|
|
|
Err(anyhow!("value is above limit of {}.", MAX_FPS))
|
|
|
|
} else if value == 0 {
|
|
|
|
Err(anyhow!("value is zero."))
|
|
|
|
} else {
|
|
|
|
Ok(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// Validates the duration argument.
|
2022-04-11 19:02:11 +00:00
|
|
|
fn validate_duration(text: &str) -> Result<u64, Error> {
|
|
|
|
let value = text.parse()?;
|
|
|
|
|
|
|
|
if value == 0 {
|
|
|
|
Err(anyhow!("value is zero."))
|
|
|
|
} else {
|
|
|
|
Ok(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// Returns the size to use based on the terminal size and width and height arguments.
|
2022-04-11 19:02:11 +00:00
|
|
|
fn size(terminal: (u16, u16), width: Option<usize>, height: Option<usize>) -> (usize, usize) {
|
|
|
|
let width = width.unwrap_or(terminal.0 as usize);
|
|
|
|
let height = height.unwrap_or(terminal.1 as usize);
|
|
|
|
(width, height)
|
|
|
|
}
|
|
|
|
|
2022-04-15 05:42:01 +00:00
|
|
|
/// Calculates the delay between frames based on the fps.
|
2022-04-11 19:02:11 +00:00
|
|
|
fn delay_of_fps(fps: u64) -> Duration {
|
2022-04-13 17:38:28 +00:00
|
|
|
Duration::from_nanos(1_000_000_000 / fps)
|
2022-04-11 19:02:11 +00:00
|
|
|
}
|
|
|
|
|
2022-04-09 10:56:57 +00:00
|
|
|
fn create_animation(animation: AnimationType, size: Vector) -> Box<dyn Animation> {
|
|
|
|
match animation {
|
2022-04-09 17:29:30 +00:00
|
|
|
AnimationType::Circle => Box::new(CircleAnimation::new(size)),
|
|
|
|
AnimationType::Rhombus => Box::new(RhombusAnimation::new(size)),
|
2022-04-15 05:58:08 +00:00
|
|
|
AnimationType::Sonar => Box::new(SonarAnimation::new(size)),
|
2022-04-09 10:56:57 +00:00
|
|
|
}
|
2022-04-02 15:05:49 +00:00
|
|
|
}
|
2022-04-09 10:56:57 +00:00
|
|
|
|
|
|
|
fn create_fill(fill: FillModeType, size: Vector) -> Box<dyn FillMode> {
|
|
|
|
match fill {
|
|
|
|
FillModeType::Circle => Box::new(CircleFillMode::new(size)),
|
|
|
|
FillModeType::Level => Box::new(LevelFillMode::new()),
|
2022-04-09 17:29:30 +00:00
|
|
|
FillModeType::Stripes => Box::new(StripesFillMode::new(size))
|
2022-04-09 10:56:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_color(color: ColorType) -> Box<dyn ColorSampler> {
|
2022-04-09 16:25:45 +00:00
|
|
|
use crossterm::style::Color::*;
|
|
|
|
|
2022-04-09 10:56:57 +00:00
|
|
|
match color {
|
|
|
|
ColorType::Red => Box::new(SimpleColorSampler::new(vec![Yellow, DarkYellow, Red])),
|
|
|
|
ColorType::Green => Box::new(SimpleColorSampler::new(vec![Cyan, DarkGreen, Green])),
|
|
|
|
ColorType::Blue => Box::new(SimpleColorSampler::new(vec![Magenta, DarkBlue, Blue])),
|
|
|
|
ColorType::LightRed => Box::new(SimpleColorSampler::new(vec![White, Yellow, Red])),
|
|
|
|
ColorType::LightGreen => Box::new(SimpleColorSampler::new(vec![White, Cyan, Green])),
|
|
|
|
ColorType::LightBlue => Box::new(SimpleColorSampler::new(vec![White, Blue, Magenta])),
|
|
|
|
ColorType::Grey => Box::new(SimpleColorSampler::new(vec![Black, Grey, White])),
|
|
|
|
ColorType::Rainbow => Box::new(SimpleColorSampler::new(vec![Magenta, Blue, Green, Yellow, Red]))
|
|
|
|
}
|
2022-04-13 17:38:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_chars_some_string() {
|
|
|
|
assert_eq!("abc", &validate_chars("abc").unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_chars_empty() {
|
|
|
|
assert!(validate_chars("").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_fps_some_string() {
|
|
|
|
assert_eq!(35, validate_fps("35").unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_fps_zero() {
|
|
|
|
assert!(validate_fps("0").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_fps_above_max() {
|
|
|
|
assert!(validate_fps("500").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_duration_some_string() {
|
|
|
|
assert_eq!(500, validate_duration("500").unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn validate_duration_zero() {
|
|
|
|
assert!(validate_duration("0").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn size_not_set() {
|
|
|
|
assert_eq!((4, 6), size((4, 6), None, None));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn size_width_set() {
|
|
|
|
assert_eq!((8, 3), size((2, 3), Some(8), None));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn size_height_set() {
|
|
|
|
assert_eq!((1, 6), size((1, 7), None, Some(6)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn delay_of_fps_some_number() {
|
|
|
|
assert_eq!(Duration::from_nanos(10_526_315), delay_of_fps(95));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn create_animation_all_implemented() {
|
|
|
|
let size = Vector::new(0.0, 0.0);
|
|
|
|
|
|
|
|
create_animation(AnimationType::Circle, size);
|
|
|
|
create_animation(AnimationType::Rhombus, size);
|
2022-04-15 05:58:08 +00:00
|
|
|
create_animation(AnimationType::Sonar, size);
|
2022-04-13 17:38:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn create_fill_all_implemented() {
|
|
|
|
let size = Vector::new(0.0, 0.0);
|
|
|
|
|
|
|
|
create_fill(FillModeType::Circle, size);
|
|
|
|
create_fill(FillModeType::Level, size);
|
|
|
|
create_fill(FillModeType::Stripes, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn create_color_all_implemented() {
|
|
|
|
create_color(ColorType::Red);
|
|
|
|
create_color(ColorType::Green);
|
|
|
|
create_color(ColorType::Blue);
|
|
|
|
create_color(ColorType::LightRed);
|
|
|
|
create_color(ColorType::LightGreen);
|
|
|
|
create_color(ColorType::LightBlue);
|
|
|
|
create_color(ColorType::Grey);
|
|
|
|
create_color(ColorType::Rainbow);
|
|
|
|
}
|
2022-04-09 10:56:57 +00:00
|
|
|
}
|