1use egui::{Color32, Context, Id, LayerId, Order, Pos2, Rect, Vec2};
2use rand::Rng;
3use std::ops::RangeInclusive;
4
5#[derive(Default, Clone)]
6struct SnowState {
7 flakes: Vec<Snowflake>,
8}
9
10#[derive(Clone, Copy)]
11struct Snowflake {
12 normalized_pos: Pos2,
14 fall_speed: f32,
16 turbulence: f32,
18 size: f32,
20 phase: f32,
22}
23
24#[derive(Debug)]
29pub struct Snow {
30 id: Id,
31 color: Color32,
32 density: usize,
33 layer_order: Order,
34 custom_layer: Option<LayerId>,
35 custom_area: Option<Rect>,
36
37 size_range: RangeInclusive<f32>,
38 speed_range: RangeInclusive<f32>,
39 wind: Vec2,
40}
41
42impl Snow {
43 pub fn new(id_source: impl std::hash::Hash) -> Self {
45 Self {
46 id: Id::new(id_source),
47 color: Color32::WHITE,
48 density: 50,
49 layer_order: Order::Foreground,
50 custom_layer: None,
51 custom_area: None,
52 size_range: 0.3..=1.5,
54 speed_range: 40.0..=100.0,
55 wind: Vec2::ZERO,
56 }
57 }
58
59 pub fn color(mut self, color: Color32) -> Self {
61 self.color = color;
62 self
63 }
64
65 pub fn density(mut self, density: usize) -> Self {
67 self.density = density;
68 self
69 }
70
71 pub fn speed(mut self, range: RangeInclusive<f32>) -> Self {
73 self.speed_range = range;
74 self
75 }
76
77 pub fn size(mut self, range: RangeInclusive<f32>) -> Self {
79 self.size_range = range;
80 self
81 }
82
83 pub fn wind(mut self, wind: impl Into<Vec2>) -> Self {
85 self.wind = wind.into();
86 self
87 }
88
89 pub fn on_top(mut self) -> Self {
91 self.layer_order = Order::Debug;
92 self
93 }
94
95 pub fn on_foreground(mut self) -> Self {
97 self.layer_order = Order::Foreground;
98 self
99 }
100
101 pub fn on_background(mut self) -> Self {
103 self.layer_order = Order::Background;
104 self
105 }
106
107 pub fn layer(mut self, layer_id: LayerId) -> Self {
111 self.custom_layer = Some(layer_id);
112 self
113 }
114
115 pub fn area(mut self, area: Rect) -> Self {
119 self.custom_area = Some(area);
120 self
121 }
122
123 pub fn show(self, ctx: &Context) {
127 let screen_rect = self.custom_area.unwrap_or_else(|| ctx.content_rect());
128 if screen_rect.width() <= 0.0 || screen_rect.height() <= 0.0 {
129 return;
130 }
131
132 let dt = ctx.input(|i| i.stable_dt).min(0.1);
133 let time = ctx.input(|i| i.time);
134
135 let mut snowflakes = ctx.data_mut(|data| {
136 let state = data.get_temp_mut_or_insert_with(self.id, SnowState::default);
137 std::mem::take(&mut state.flakes)
138 });
139
140 let mut rng = rand::rng();
142 if snowflakes.len() < self.density {
143 let needed = self.density - snowflakes.len();
144 for _ in 0..needed {
145 snowflakes.push(Snowflake::spawn(
146 &mut rng,
147 true,
148 &self.size_range,
149 &self.speed_range,
150 ));
151 }
152 } else if snowflakes.len() > self.density {
153 snowflakes.truncate(self.density);
154 }
155
156 let layer_id = self
157 .custom_layer
158 .unwrap_or_else(|| LayerId::new(self.layer_order, self.id));
159 let painter = ctx.layer_painter(layer_id);
160
161 let min_size = *self.size_range.start();
163 let size_diff = (*self.size_range.end() - min_size).max(0.0001);
164
165 for flake in &mut snowflakes {
167 let pixel_dy = (flake.fall_speed + self.wind.y) * dt;
170
171 let sway = ((time as f32 + flake.phase).sin()) * 5.0;
173 let pixel_dx = (flake.turbulence + self.wind.x + sway) * dt;
174
175 flake.normalized_pos.x += pixel_dx / screen_rect.width();
176 flake.normalized_pos.y += pixel_dy / screen_rect.height();
177
178 if flake.normalized_pos.y > 1.0 {
181 *flake = Snowflake::spawn(&mut rng, false, &self.size_range, &self.speed_range);
182 }
183 else if flake.normalized_pos.y < -0.05 {
185 flake.normalized_pos.y = 1.0;
186 flake.normalized_pos.x = rng.random_range(0.0..1.0);
187 flake.size = rng.random_range(self.size_range.clone());
188 flake.fall_speed = rng.random_range(self.speed_range.clone());
189 }
190
191 if flake.normalized_pos.x > 1.0 {
193 flake.normalized_pos.x -= 1.0;
194 } else if flake.normalized_pos.x < 0.0 {
195 flake.normalized_pos.x += 1.0;
196 }
197
198 let pixel_pos = Pos2::new(
200 screen_rect.min.x + flake.normalized_pos.x * screen_rect.width(),
201 screen_rect.min.y + flake.normalized_pos.y * screen_rect.height(),
202 );
203
204 let depth_factor = (flake.size - min_size) / size_diff;
207 let alpha_mult = 0.4 + (0.6 * depth_factor); let final_color = self.color.gamma_multiply(alpha_mult);
209
210 painter.circle_filled(pixel_pos, flake.size, final_color);
211 }
212
213 ctx.data_mut(|data| {
215 let state = data.get_temp_mut_or_insert_with(self.id, SnowState::default);
216 state.flakes = snowflakes;
217 });
218
219 ctx.request_repaint();
220 }
221}
222
223impl Snowflake {
224 fn spawn(
225 rng: &mut impl rand::Rng,
226 random_y: bool,
227 size_range: &RangeInclusive<f32>,
228 speed_range: &RangeInclusive<f32>,
229 ) -> Self {
230 Self {
231 normalized_pos: Pos2::new(
232 rng.random_range(0.0..1.0),
233 if random_y {
234 rng.random_range(0.0..1.0)
235 } else {
236 -0.05
237 },
238 ),
239 fall_speed: rng.random_range(speed_range.clone()),
240 turbulence: rng.random_range(-20.0..20.0),
241 size: rng.random_range(size_range.clone()),
242 phase: rng.random_range(0.0..std::f32::consts::PI * 2.0),
243 }
244 }
245}