heart/
heart.rs

1use std::{
2    f32::consts::PI,
3    io::{stdout, Result},
4    thread::sleep,
5    time::{Duration, SystemTime},
6};
7
8use crossterm::{
9    cursor,
10    event::{self, KeyCode},
11    execute, terminal,
12};
13use firework_rs::{
14    config::Config,
15    fireworks::{ExplosionForm, Firework, FireworkConfig, FireworkManager},
16    particle::ParticleConfig,
17    term::Terminal,
18    utils::gen_points_fan,
19};
20use glam::Vec2;
21use rand::{seq::IteratorRandom, thread_rng, Rng};
22
23fn main() -> Result<()> {
24    let mut stdout = stdout();
25    let (_width, _height) = terminal::size()?;
26    let mut is_running = true;
27    let cfg = Config::default();
28
29    terminal::enable_raw_mode()?;
30    execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?;
31
32    let mut time = SystemTime::now();
33    let mut term = Terminal::default();
34    let mut fm = FireworkManager::default().with_firework(gen_heart_firework(Vec2::new(
35        _width as f32 / 4.,
36        _height as f32 / 2.,
37    )));
38
39    while is_running {
40        if event::poll(Duration::ZERO)? {
41            match event::read()? {
42                event::Event::Key(e) => {
43                    if e.code == KeyCode::Esc {
44                        is_running = false;
45                    }
46                }
47                event::Event::Resize(_, _) => {
48                    fm.reset();
49                    term.reinit(&cfg);
50                }
51                _ => {}
52            };
53        }
54
55        let delta_time = SystemTime::now().duration_since(time).unwrap();
56        fm.update(time, delta_time);
57        time = SystemTime::now();
58
59        term.render(&fm, &cfg);
60        term.print(&mut stdout, &cfg);
61
62        if delta_time < Duration::from_secs_f32(0.05) {
63            let rem = Duration::from_secs_f32(0.05) - delta_time;
64            sleep(rem);
65        }
66    }
67
68    execute!(stdout, cursor::Show, terminal::LeaveAlternateScreen)?;
69    terminal::disable_raw_mode()?;
70
71    Ok(())
72}
73
74fn gen_heart_firework(center: Vec2) -> Firework {
75    let colors = vec![
76        (233, 232, 237),
77        (254, 142, 130),
78        (200, 27, 72),
79        (86, 18, 31),
80    ];
81    let mut particles = Vec::new();
82    let trail_length = thread_rng().gen_range(100..105);
83    let life_time = Duration::from_secs_f32(thread_rng().gen_range(3.0..3.2));
84    let init_pos = center - Vec2::NEG_Y * 15.;
85    for v in gen_points_fan(300., 45, 0.2 * PI, 0.3 * PI).iter() {
86        particles.push(ParticleConfig::new(
87            init_pos,
88            *v,
89            trail_length,
90            life_time,
91            *colors.iter().choose(&mut thread_rng()).unwrap(),
92        ));
93    }
94    for v in gen_points_fan(300., 45, 0.7 * PI, 0.8 * PI).iter() {
95        particles.push(ParticleConfig::new(
96            init_pos,
97            *v,
98            trail_length,
99            life_time,
100            *colors.iter().choose(&mut thread_rng()).unwrap(),
101        ));
102    }
103    let mut config = FireworkConfig::default()
104        .with_ar_scale(0.1)
105        .with_gravity_scale(0.1)
106        .with_gradient_scale(gradient)
107        .with_additional_force(move |particle| (center - particle.pos) * 2.);
108    config.set_enable_gradient(true);
109    Firework {
110        init_time: SystemTime::now(),
111        spawn_after: Duration::ZERO,
112        center,
113        particles,
114        config,
115        form: ExplosionForm::Instant { used: false },
116        ..Default::default()
117    }
118}
119
120fn gradient(x: f32) -> f32 {
121    if x < 0.8125 {
122        -0.4 * x + 1.1
123    } else {
124        -2. * x + 2.2
125    }
126}