1use std::env;
2use std::io::{self, Write};
3use std::str::FromStr;
4use std::thread;
5use std::time::Duration;
6
7use aisling::{Effect, EffectConfig, EffectKind};
8
9fn main() {
10 let args = Args::parse();
11 let kind = EffectKind::from_str(&args.effect).unwrap_or(EffectKind::Wipe);
12 let mut config = EffectConfig::default()
13 .with_duration(args.duration)
14 .with_seed(args.seed);
15
16 if let (Some(width), Some(height)) = (args.width, args.height) {
17 config = config.with_canvas_size(width.max(1), height.max(1));
18 }
19
20 let effect = Effect::with_config(kind, args.text, config);
21
22 let mut stdout = io::stdout();
23 for frame in effect.iter() {
24 write!(stdout, "\x1b[2J\x1b[H{}", frame.to_ansi_string()).unwrap();
25 stdout.flush().unwrap();
26 thread::sleep(Duration::from_millis((1000 / args.fps.max(1)) as u64));
27 }
28 writeln!(stdout).unwrap();
29}
30
31struct Args {
32 effect: String,
33 text: String,
34 width: Option<usize>,
35 height: Option<usize>,
36 fps: u64,
37 duration: usize,
38 seed: u64,
39}
40
41impl Args {
42 fn parse() -> Self {
43 let mut width = None;
44 let mut height = None;
45 let mut fps = 24u64;
46 let mut duration = None;
47 let mut seconds = None;
48 let mut seed = 7u64;
49 let mut text = None;
50 let mut positional: Vec<String> = Vec::new();
51
52 let mut args = env::args().skip(1).peekable();
53 while let Some(arg) = args.next() {
54 match arg.as_str() {
55 "--width" => {
56 width = args.next().and_then(|value| value.parse::<usize>().ok());
57 }
58 "--height" => {
59 height = args.next().and_then(|value| value.parse::<usize>().ok());
60 }
61 "--fps" => {
62 fps = args
63 .next()
64 .and_then(|value| value.parse::<u64>().ok())
65 .unwrap_or(fps);
66 }
67 "--duration" => {
68 duration = args
69 .next()
70 .and_then(|value| value.parse::<usize>().ok())
71 .map(|value| value.max(1));
72 }
73 "--seconds" => {
74 seconds = args
75 .next()
76 .and_then(|value| value.parse::<u64>().ok())
77 .map(|value| value.max(1));
78 }
79 "--seed" => {
80 seed = args
81 .next()
82 .and_then(|value| value.parse::<u64>().ok())
83 .unwrap_or(seed);
84 }
85 "--text" => {
86 text = args.next();
87 }
88 "-h" | "--help" | "help" => {
89 usage();
90 std::process::exit(0);
91 }
92 _ if !arg.starts_with('-') => {
93 positional.push(arg);
94 }
95 _ => {
96 eprintln!("Ignoring unknown argument: {arg}");
97 }
98 }
99 }
100
101 let effect = positional
102 .first()
103 .cloned()
104 .unwrap_or_else(|| "wipe".to_string());
105 let text = text
106 .or_else(|| positional.get(1).cloned())
107 .unwrap_or_else(|| "Aisling".to_string());
108 let duration = duration
109 .or_else(|| seconds.map(|value| (value as usize).saturating_mul(fps as usize)))
110 .unwrap_or(90);
111
112 Self {
113 effect,
114 text,
115 width,
116 height,
117 fps,
118 duration,
119 seed,
120 }
121 }
122}
123
124fn usage() {
125 println!("Usage:");
126 println!(" cargo run --example demo [effect] [text]");
127 println!(" cargo run --example demo -- [effect] [text] --width <cols> --height <rows> --fps <n> --duration <frames>");
128 println!(" cargo run --example demo -- [effect] --text <text> --seconds <seconds> --fps <n>");
129 println!();
130 println!("Examples:");
131 println!(" cargo run --example demo -- wipe \"Aisling\"");
132 println!(" cargo run --example demo -- matrix \"Hello\" --width 120 --height 30 --fps 24");
133}