use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
widgets::Widget,
};
use rand::Rng;
use std::collections::{HashMap, VecDeque};
pub struct SimpleMatrixRain {
columns: HashMap<u16, Column>,
width: u16,
height: u16,
tick: u64,
active_ips: VecDeque<(String, u32)>, }
struct Column {
head: i16,
tail: i16,
speed: u8,
chars: Vec<char>,
}
impl Column {
fn new(height: u16) -> Self {
let mut rng = rand::thread_rng();
let speed = rng.gen_range(1..4); let length = rng.gen_range(5..20);
let chars: Vec<char> = (0..height + 20)
.map(|_| {
let c = match rng.gen_range(0..10) {
0..=3 => rng.gen_range(b'0'..=b'9') as char,
4..=6 => rng.gen_range(b'A'..=b'Z') as char,
_ => rng.gen_range(b'a'..=b'z') as char,
};
c
})
.collect();
Self {
head: -(rng.gen_range(0..20)),
tail: -(length as i16),
speed,
chars,
}
}
fn update(&mut self) {
if self.speed > 0 {
self.head += 1;
self.tail += 1;
}
}
fn should_reset(&self, height: u16) -> bool {
self.tail > height as i16
}
}
impl SimpleMatrixRain {
pub fn new(width: u16, height: u16) -> Self {
let mut columns = HashMap::new();
let mut rng = rand::thread_rng();
for _ in 0..width / 3 {
let x = rng.gen_range(0..width);
if !columns.contains_key(&x) {
columns.insert(x, Column::new(height));
}
}
Self {
columns,
width,
height,
tick: 0,
active_ips: VecDeque::new(),
}
}
pub fn update(&mut self) {
self.tick += 1;
let mut rng = rand::thread_rng();
let mut to_remove = Vec::new();
for (x, column) in self.columns.iter_mut() {
if self.tick % column.speed as u64 == 0 {
column.update();
if column.should_reset(self.height) {
to_remove.push(*x);
}
}
}
for x in to_remove {
self.columns.remove(&x);
}
if rng.gen_bool(0.1) && self.columns.len() < (self.width as usize * 2 / 3) {
let x = rng.gen_range(0..self.width);
if !self.columns.contains_key(&x) {
self.columns.insert(x, Column::new(self.height));
}
}
}
pub fn add_column(&mut self, x: u16) {
if x < self.width && !self.columns.contains_key(&x) {
self.columns.insert(x, Column::new(self.height));
}
}
pub fn track_ip_packet(&mut self, src_ip: &str, dst_ip: &str, _protocol: &str) {
for ip in [src_ip, dst_ip] {
let mut found = false;
for (existing_ip, count) in &mut self.active_ips {
if existing_ip == ip {
*count += 1;
found = true;
break;
}
}
if !found {
if self.active_ips.len() < 5 {
self.active_ips.push_back((ip.to_string(), 1));
} else {
if let Some(_) = self.active_ips.pop_back() {
self.active_ips.push_front((ip.to_string(), 1));
}
}
}
}
let mut sorted: Vec<_> = self.active_ips.drain(..).collect();
sorted.sort_by(|a, b| b.1.cmp(&a.1));
sorted.truncate(5);
self.active_ips = sorted.into_iter().collect();
let mut rng = rand::thread_rng();
let x = rng.gen_range(0..self.width);
if !self.columns.contains_key(&x) && self.columns.len() < (self.width as usize / 2) {
self.add_column(x);
}
}
pub fn get_active_ips(&self) -> Vec<(String, u32)> {
self.active_ips.iter().cloned().collect()
}
}
impl Widget for &SimpleMatrixRain {
fn render(self, area: Rect, buf: &mut Buffer) {
for (col_x, column) in &self.columns {
if *col_x >= area.width {
continue;
}
for y in 0..area.height {
let char_pos = y as i16;
if char_pos >= column.tail && char_pos <= column.head {
let char_idx = (char_pos as usize) % column.chars.len();
let ch = column.chars[char_idx];
let color = if char_pos == column.head {
Color::White
} else {
Color::Green
};
if area.x + col_x < buf.area.width && area.y + y < buf.area.height {
buf.get_mut(area.x + col_x, area.y + y)
.set_char(ch)
.set_style(Style::default().fg(color));
}
}
}
}
}
}