egui_elements/widgets/particles/snow/
settings.rs1use egui::{Color32, Context, Slider, Ui, Vec2};
4
5use super::super::shared::{ParticleLayer, begin_body, settings_window};
6
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Debug, Clone, PartialEq)]
14pub struct SnowSettings {
15 pub open: bool,
17 pub active: bool,
19 pub color: Color32,
21 pub density: usize,
23 pub size_min: f32,
25 pub size_max: f32,
27 pub speed_min: f32,
29 pub speed_max: f32,
31 pub wind: Vec2,
33 pub layer: ParticleLayer,
35}
36
37impl Default for SnowSettings {
38 fn default() -> Self {
39 Self {
40 open: false,
41 active: true,
42 color: Color32::from_white_alpha(200),
43 density: 400,
44 size_min: 0.3,
45 size_max: 1.0,
46 speed_min: 40.0,
47 speed_max: 120.0,
48 wind: Vec2::ZERO,
49 layer: ParticleLayer::Foreground,
50 }
51 }
52}
53
54impl SnowSettings {
55 pub fn reset(&mut self) {
57 let open = self.open;
58 *self = Self::default();
59 self.open = open;
60 }
61
62 pub fn show_window(&mut self, ctx: &Context) -> Option<egui::Response> {
67 let mut open = self.open;
69 let resp = settings_window(ctx, "Snow Settings", &mut open, |ui| {
70 self.body_ui(ui)
71 });
72 self.open = open;
73 resp
74 }
75
76 pub fn body_ui(&mut self, ui: &mut Ui) {
82 let theme = begin_body(ui);
83
84 ui.heading("General");
85 ui.checkbox(&mut self.active, "Effect Active");
86 ui.horizontal(|ui| {
87 ui.label("Color:");
88 ui.color_edit_button_srgba(&mut self.color);
89 });
90
91 ui.separator();
92 ui.heading("Particles");
93
94 ui.horizontal(|ui| {
95 ui.label("Density (Count):");
96 ui.add(Slider::new(&mut self.density, 0..=20000).logarithmic(true));
97 });
98
99 ui.horizontal(|ui| {
100 ui.label("Size (px):");
101 ui.add(
102 egui::DragValue::new(&mut self.size_min)
103 .speed(0.1)
104 .range(0.1..=20.0)
105 .prefix("Min: "),
106 );
107 ui.add(
108 egui::DragValue::new(&mut self.size_max)
109 .speed(0.1)
110 .range(0.1..=20.0)
111 .prefix("Max: "),
112 );
113 });
114 if self.size_min > self.size_max {
116 self.size_min = self.size_max;
117 }
118
119 ui.separator();
120 ui.heading("Physics");
121
122 ui.horizontal(|ui| {
123 ui.label("Fall Speed (px/s):");
124 ui.add(
125 egui::DragValue::new(&mut self.speed_min)
126 .speed(1.0)
127 .range(0.0..=500.0)
128 .prefix("Min: "),
129 );
130 ui.add(
131 egui::DragValue::new(&mut self.speed_max)
132 .speed(1.0)
133 .range(0.0..=500.0)
134 .prefix("Max: "),
135 );
136 });
137 if self.speed_min > self.speed_max {
138 self.speed_min = self.speed_max;
139 }
140
141 ui.label("Wind Vector:");
142 ui.horizontal(|ui| {
143 ui.add(Slider::new(&mut self.wind.x, -200.0..=200.0).text("Horizontal"));
144 });
145 ui.horizontal(|ui| {
146 ui.add(Slider::new(&mut self.wind.y, -100.0..=100.0).text("Vertical"));
147 });
148
149 ui.separator();
150 ui.heading("Rendering");
151 ui.horizontal(|ui| {
152 ui.label("Layer:");
153 ui.selectable_value(
154 &mut self.layer,
155 ParticleLayer::Background,
156 ParticleLayer::Background.to_str(),
157 )
158 .on_hover_text("Behind everything (Order::Background)");
159 ui.selectable_value(
160 &mut self.layer,
161 ParticleLayer::Foreground,
162 ParticleLayer::Foreground.to_str(),
163 )
164 .on_hover_text("In front of windows (Order::Foreground)");
165 ui.selectable_value(
166 &mut self.layer,
167 ParticleLayer::Top,
168 ParticleLayer::Top.to_str(),
169 )
170 .on_hover_text("On top of everything including debug/tooltips (Order::Debug)");
171 });
172
173 ui.add_space(theme.spacing.sm);
174 if ui.button("Reset Defaults").clicked() {
175 self.reset();
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use egui::__run_test_ctx;
184
185 #[test]
186 fn defaults_match_upstream_demo() {
187 let settings = SnowSettings::default();
188 assert!(!settings.open);
189 assert!(settings.active);
190 assert_eq!(settings.color, Color32::from_white_alpha(200));
191 assert_eq!(settings.density, 400);
192 assert_eq!(settings.size_min, 0.3);
193 assert_eq!(settings.size_max, 1.0);
194 assert_eq!(settings.speed_min, 40.0);
195 assert_eq!(settings.speed_max, 120.0);
196 assert_eq!(settings.wind, Vec2::ZERO);
197 assert_eq!(settings.layer, ParticleLayer::Foreground);
198 }
199
200 #[test]
201 fn reset_keeps_open() {
202 let mut settings = SnowSettings {
203 open: true,
204 active: false,
205 density: 3,
206 size_min: 9.0,
207 size_max: 11.0,
208 speed_min: 5.0,
209 speed_max: 6.0,
210 wind: Vec2::new(7.0, 8.0),
211 layer: ParticleLayer::Top,
212 color: Color32::from_rgb(9, 9, 9),
213 ..Default::default()
214 };
215
216 settings.reset();
217
218 assert!(settings.open, "reset must not close the window");
219 assert_eq!(
220 settings,
221 SnowSettings {
222 open: true,
223 ..Default::default()
224 }
225 );
226 }
227
228 fn window_height_on(screen: Vec2) -> f32 {
234 let ctx = Context::default();
235 let mut theme = crate::theme::Theme::new(crate::theme::ThemeKind::TokyoNight);
238 theme.install(&ctx);
239 let mut settings = SnowSettings {
240 open: true,
241 ..Default::default()
242 };
243 let mut size = Vec2::ZERO;
244 for _ in 0..2 {
245 let output = ctx.run_ui(
246 egui::RawInput {
247 screen_rect: Some(egui::Rect::from_min_size(
248 egui::Pos2::ZERO,
249 screen,
250 )),
251 ..Default::default()
252 },
253 |ui| {
254 if let Some(resp) = settings.show_window(ui.ctx()) {
255 size = resp.rect.size();
256 }
257 },
258 );
259 output.drop_without_applying_deltas();
260 }
261 size.y
262 }
263
264 #[test]
265 fn settings_body_fits_the_demo_root() {
266 let height = window_height_on(Vec2::new(900.0, 2000.0));
267 assert!(
268 height <= 650.0,
269 "settings window is {height} tall, root is 650"
270 );
271 }
272
273 #[test]
274 fn settings_window_never_exceeds_a_short_root() {
275 let height = window_height_on(Vec2::new(900.0, 400.0));
276 assert!(
277 height <= 400.0,
278 "settings window is {height} tall, root is 400"
279 );
280 }
281
282 #[test]
283 fn body_ui_renders_without_a_window() {
284 let mut settings = SnowSettings::default();
285 assert!(!settings.open);
286 egui::__run_test_ui(|ui| {
287 settings.body_ui(ui);
288 });
289 assert!(
290 !settings.open,
291 "body_ui must not touch the window flag"
292 );
293 }
294
295 #[test]
296 fn show_window_returns_response_only_when_open() {
297 __run_test_ctx(|ctx| {
298 let mut closed = SnowSettings::default();
299 assert!(closed.show_window(ctx).is_none());
300 });
301
302 __run_test_ctx(|ctx| {
303 let mut open = SnowSettings {
304 open: true,
305 ..Default::default()
306 };
307 assert!(open.show_window(ctx).is_some());
308 });
309 }
310}