1use ratatui::{
4 Frame,
5 text::{Line, Span},
6 widgets::Paragraph,
7};
8use sigye_core::{AnimationSpeed, BackgroundStyle, SystemMetrics};
9
10use crate::animations::{matrix, reactive, stateless, weather};
11
12#[derive(Debug)]
14pub struct BackgroundState {
15 matrix_columns: Vec<matrix::MatrixColumn>,
17 snow_columns: Vec<weather::SnowColumn>,
19 rain_columns: Vec<weather::RainColumn>,
21 storm_state: Option<weather::StormState>,
23 wind_streaks: Vec<weather::WindStreak>,
25 last_width: u16,
27 last_height: u16,
29 last_update_ms: u64,
31 init_seed: u64,
33}
34
35impl Default for BackgroundState {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl BackgroundState {
42 pub fn new() -> Self {
44 use std::time::{SystemTime, UNIX_EPOCH};
45
46 let init_seed = SystemTime::now()
48 .duration_since(UNIX_EPOCH)
49 .map(|d| d.as_nanos() as u64)
50 .unwrap_or(0);
51
52 Self {
53 matrix_columns: Vec::new(),
54 snow_columns: Vec::new(),
55 rain_columns: Vec::new(),
56 storm_state: None,
57 wind_streaks: Vec::new(),
58 last_width: 0,
59 last_height: 0,
60 last_update_ms: 0,
61 init_seed,
62 }
63 }
64
65 pub fn render(
67 &mut self,
68 frame: &mut Frame,
69 style: BackgroundStyle,
70 elapsed_ms: u64,
71 speed: AnimationSpeed,
72 metrics: Option<&SystemMetrics>,
73 ) {
74 if style == BackgroundStyle::None {
75 return;
76 }
77
78 let area = frame.area();
79 let width = area.width;
80 let height = area.height;
81
82 if style.is_reactive() {
84 if let Some(m) = metrics {
85 self.render_reactive(frame, style, elapsed_ms, speed, m);
86 }
87 return;
88 }
89
90 let dimensions_changed = width != self.last_width || height != self.last_height;
92
93 if style == BackgroundStyle::MatrixRain
94 && (dimensions_changed || self.matrix_columns.is_empty())
95 {
96 self.matrix_columns = matrix::init_columns(width, height);
97 }
98 if style == BackgroundStyle::Snowfall
99 && (dimensions_changed || self.snow_columns.is_empty())
100 {
101 self.snow_columns = weather::init_snow_columns(width, height, self.init_seed);
102 }
103 if style == BackgroundStyle::Rainy && (dimensions_changed || self.rain_columns.is_empty()) {
105 self.rain_columns = weather::init_rain_columns(width, height, self.init_seed);
106 }
107 if style == BackgroundStyle::Stormy && (dimensions_changed || self.storm_state.is_none()) {
108 self.storm_state = Some(weather::init_storm(width, height, self.init_seed));
109 }
110 if style == BackgroundStyle::Windy && (dimensions_changed || self.wind_streaks.is_empty()) {
111 self.wind_streaks = weather::init_wind_streaks(width, height, self.init_seed);
112 }
113
114 if dimensions_changed {
115 self.last_width = width;
116 self.last_height = height;
117 }
118
119 let delta_ms = elapsed_ms.saturating_sub(self.last_update_ms);
121 self.last_update_ms = elapsed_ms;
122
123 if style == BackgroundStyle::MatrixRain {
125 matrix::update(&mut self.matrix_columns, delta_ms, height, speed);
126 }
127 if style == BackgroundStyle::Snowfall {
128 weather::update_snow(&mut self.snow_columns, delta_ms, height, speed);
129 }
130 if style == BackgroundStyle::Rainy {
132 weather::update_rain(&mut self.rain_columns, delta_ms, height, speed);
133 }
134 if style == BackgroundStyle::Stormy
135 && let Some(ref mut storm) = self.storm_state
136 {
137 weather::update_storm(storm, elapsed_ms, delta_ms, height, speed);
138 }
139 if style == BackgroundStyle::Windy {
140 weather::update_wind(&mut self.wind_streaks, delta_ms, width, height, speed);
141 }
142
143 let lines: Vec<Line> = (0..height)
144 .map(|y| {
145 let spans: Vec<Span> = (0..width)
146 .map(|x| self.render_char(x, y, width, height, style, elapsed_ms, speed))
147 .collect();
148 Line::from(spans)
149 })
150 .collect();
151
152 frame.render_widget(Paragraph::new(lines), area);
153 }
154
155 fn render_char(
157 &self,
158 x: u16,
159 y: u16,
160 width: u16,
161 height: u16,
162 style: BackgroundStyle,
163 elapsed_ms: u64,
164 speed: AnimationSpeed,
165 ) -> Span<'static> {
166 match style {
167 BackgroundStyle::None => Span::raw(" "),
168 BackgroundStyle::Starfield => stateless::render_starfield_char(x, y, elapsed_ms, speed),
169 BackgroundStyle::MatrixRain => matrix::render_char(&self.matrix_columns, x, y),
170 BackgroundStyle::GradientWave => {
171 stateless::render_gradient_char(x, y, width, height, elapsed_ms, speed)
172 }
173 BackgroundStyle::Snowfall => {
174 weather::render_snow_char(&self.snow_columns, x, y, elapsed_ms)
175 }
176 BackgroundStyle::Frost => {
177 stateless::render_frost_char(x, y, width, height, elapsed_ms, speed)
178 }
179 BackgroundStyle::Aurora => {
180 stateless::render_aurora_char(x, y, width, height, elapsed_ms, speed)
181 }
182 BackgroundStyle::Sunny => {
184 weather::render_sunny_char(x, y, width, height, elapsed_ms, speed)
185 }
186 BackgroundStyle::Rainy => weather::render_rain_char(&self.rain_columns, x, y),
187 BackgroundStyle::Stormy => {
188 if let Some(ref storm) = self.storm_state {
189 weather::render_storm_char(storm, x, y, elapsed_ms)
190 } else {
191 Span::raw(" ")
192 }
193 }
194 BackgroundStyle::Windy => {
195 weather::render_wind_char(&self.wind_streaks, x, y, elapsed_ms)
196 }
197 BackgroundStyle::Cloudy => {
198 weather::render_cloudy_char(x, y, width, height, elapsed_ms, speed)
199 }
200 BackgroundStyle::Foggy => {
201 weather::render_foggy_char(x, y, width, height, elapsed_ms, speed)
202 }
203 BackgroundStyle::Weather => stateless::render_starfield_char(x, y, elapsed_ms, speed),
206 BackgroundStyle::TwilightDawn => {
208 stateless::render_twilight_dawn_char(x, y, width, height, elapsed_ms, speed)
209 }
210 BackgroundStyle::TwilightDusk => {
211 stateless::render_twilight_dusk_char(x, y, width, height, elapsed_ms, speed)
212 }
213 BackgroundStyle::SystemPulse
215 | BackgroundStyle::ResourceWave
216 | BackgroundStyle::DataFlow
217 | BackgroundStyle::HeatMap => Span::raw(" "),
218 }
219 }
220
221 fn render_reactive(
223 &mut self,
224 frame: &mut Frame,
225 style: BackgroundStyle,
226 elapsed_ms: u64,
227 speed: AnimationSpeed,
228 metrics: &SystemMetrics,
229 ) {
230 match style {
231 BackgroundStyle::SystemPulse => {
232 reactive::render_system_pulse(frame, elapsed_ms, speed, metrics)
233 }
234 BackgroundStyle::ResourceWave => {
235 reactive::render_resource_wave(frame, elapsed_ms, speed, metrics)
236 }
237 BackgroundStyle::DataFlow => {
238 reactive::render_data_flow(frame, elapsed_ms, speed, metrics)
239 }
240 BackgroundStyle::HeatMap => {
241 reactive::render_heat_map(frame, elapsed_ms, speed, metrics)
242 }
243 _ => {}
244 }
245 }
246}