use std::time::{Duration, SystemTime};
use rusty_bubbletea::model::Model as ModelTrait;
use rusty_bubbletea::options::ProgramOptions;
use rusty_bubbletea::screen::WindowSizeMsg;
use rusty_bubbletea::{quit, tick, Cmd, KeyPressMsg, Msg, Program, View};
use rusty_lipgloss::{new_style, Color};
struct Rng(u64);
impl Rng {
fn seeded() -> Rng {
let seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0x9E37_79B9_7F4A_7C15);
Rng(seed)
}
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn float64(&mut self) -> f64 {
((self.next() >> 11) as f64) / ((1u64 << 53) as f64)
}
}
#[derive(Debug)]
struct TickMsg;
fn tick_cmd() -> Cmd {
tick(Duration::from_micros(1_000_000 / 60), |_| {
Some(Box::new(TickMsg))
})
}
struct Model {
colors: Vec<Vec<Color>>,
last_width: usize,
last_height: usize,
frame_count: usize,
width: usize,
height: usize,
rng: Rng,
}
impl ModelTrait for Model {
fn init(&self) -> Cmd {
tick_cmd()
}
fn update(&mut self, msg: &dyn Msg) -> Cmd {
if let Some(k) = msg.as_any().downcast_ref::<KeyPressMsg>() {
match k.0.to_string().as_str() {
"q" | "ctrl+c" => return quit(),
_ => {}
}
}
if let Some(ws) = msg.as_any().downcast_ref::<WindowSizeMsg>() {
self.width = ws.width;
self.height = ws.height;
if self.width != self.last_width || self.height != self.last_height {
self.setup_colors();
self.last_width = self.width;
self.last_height = self.height;
}
}
if msg.as_any().is::<TickMsg>() {
self.frame_count += 1;
return tick_cmd();
}
None
}
fn view(&self) -> View {
let title = new_style().bold(true).render("Space");
let mut s = String::new();
let height = self.height.saturating_sub(1); for y in 0..height {
for x in 0..self.width {
let xi = (x + self.frame_count) % self.width;
let fg = self.colors[y * 2][xi].clone();
let bg = self.colors[y * 2 + 1][xi].clone();
let st = new_style().foreground_color(fg).background_color(bg);
s.push_str(&st.render("▀"));
}
if y < height - 1 {
s.push('\n');
}
}
let mut v = View::new(&format!("{}\n{}", title, s));
v.alt_screen = true;
v
}
}
impl Model {
fn setup_colors(&mut self) {
let height = self.height * 2; self.colors = vec![vec![Color::NoColor; self.width]; height];
for y in 0..height {
let randomness_factor = (height - y) as f64 / height as f64;
for x in 0..self.width {
let base_value = randomness_factor * (height - y) as f64 / height as f64;
let random_offset = (self.rng.float64() * 0.2) - 0.1;
let value = clamp(base_value + random_offset, 0.0, 1.0);
let gray = (value * 255.0) as u8;
self.colors[y][x] = Color::parse(&format!("#{:02x}{:02x}{:02x}", gray, gray, gray));
}
}
}
}
fn clamp(value: f64, min: f64, max: f64) -> f64 {
if value < min {
return min;
}
if value > max {
return max;
}
value
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let p = Program::new(Model {
colors: Vec::new(),
last_width: 0,
last_height: 0,
frame_count: 0,
width: 0,
height: 0,
rng: Rng::seeded(),
})
.with_options(ProgramOptions::default().with_fps(120));
p.run()?;
Ok(())
}