use rand::{self, Rng, rngs, seq::SliceRandom};
use crossterm::style::{self, Stylize};
use self::color_algorithms::ColorAlgorithm;
pub mod charsets;
pub mod color_algorithms;
const FOLLOWER_MIN_LENGTH: u16 = 4;
const FOLLOWER_MAX_LENGTH_OFFSET: u16 = 4;
const START_OFFSET_RANGE: std::ops::RangeInclusive<i32> = -64..=-1;
pub struct Raindrop<'a, T>
where T: ColorAlgorithm
{
follower_content: Vec<char>,
row_index: i32,
charset: &'a Vec<char>,
advance_chance: f64,
color_algorithm: T,
local_rng: rngs::ThreadRng
}
impl<'a, T> Raindrop<'a, T>
where T: ColorAlgorithm
{
pub fn gen_char(&mut self) -> char
{
*(self.charset.choose(&mut self.local_rng).unwrap())
}
pub fn new(charset: &'a Vec<char>, color_algorithm: T, advance_chance: f64, terminal_height: u16) -> Self
{
assert!(advance_chance > 0.0, "Attempted to set advance chance at 0 or below");
assert!(advance_chance <= 1.0, "Attempted to set advance chance greater than 1");
let mut new_instance = Self {
charset,
color_algorithm,
local_rng: rand::thread_rng(),
follower_content: Vec::new(),
row_index: 0,
advance_chance
};
new_instance.reinit_state(terminal_height);
new_instance
}
pub fn reinit_state(&mut self, terminal_height: u16)
{
let max_follower_length = terminal_height.saturating_sub(FOLLOWER_MAX_LENGTH_OFFSET)
.max(FOLLOWER_MIN_LENGTH + 1);
let follower_length = self.local_rng.gen_range(FOLLOWER_MIN_LENGTH..=max_follower_length);
let mut new_follower_content = Vec::with_capacity(follower_length.into());
for _ in 0..follower_length{
new_follower_content.push(self.gen_char());
}
self.follower_content = new_follower_content;
self.row_index = self.local_rng.gen_range(START_OFFSET_RANGE);
}
pub fn get_char_at_row(&mut self, row_index: u16) -> Option<char>
{
let provided_row_index: i32 = row_index.into();
if self.row_index < provided_row_index{
return None;
}
if self.row_index == provided_row_index {
return Some(self.gen_char());
}
match TryInto::<usize>::try_into((self.row_index - 1) - provided_row_index)
{
Err(_) => {
eprintln!("Failed to represent follower_index ({}) as a usize; skipping char",
(self.row_index - 1) - provided_row_index);
return None
},
Ok(follower_index) => {
match self.follower_content.get(follower_index) {
Some(&val) => Some(val),
None => None
}
}
}
}
pub fn get_styled_char_at_row(&mut self, row_index: u16) -> Option<style::StyledContent<char>>
{
match self.get_char_at_row(row_index){
None => None,
Some(unstyled_char) => {
if self.row_index == row_index.into() {
Some(unstyled_char.with(style::Color::White)
.attribute(style::Attribute::Bold))
} else {
let position_in_follower = ((self.row_index - 1) - (row_index as i32)) as f32;
let follower_length: f32 = self.follower_content.len() as f32;
let follower_proportion = (position_in_follower/follower_length).min(1.0).max(0.0);
let char_color =
self.color_algorithm.gen_color(follower_proportion);
Some(unstyled_char.with(char_color.into()))
}
}
}
}
pub fn move_drop(&mut self)
{
self.row_index += 1;
}
pub fn is_visible(&self, terminal_height: u16) -> bool
{
if self.row_index < 0 {
return false;
}
self.row_index < (terminal_height as i32) + (self.follower_content.len() as i32)
}
pub fn advance_animation(&mut self, terminal_height: u16)
{
if !(self.row_index < 0) {
if !self.is_visible(terminal_height){
self.reinit_state(terminal_height);
return;
}
}
if self.advance_chance == 1.0 {
self.move_drop();
}
else if self.local_rng.gen_bool(self.advance_chance) {
self.move_drop();
}
}
}