1use crate::error::Result;
7use crate::preset::MilkPreset;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct DoublePreset {
13 pub preset_a: MilkPreset,
15
16 pub preset_b: MilkPreset,
18
19 pub blend_pattern: BlendPattern,
21
22 pub blend_amount: f32,
24
25 pub animate_blend: bool,
27
28 pub animation_speed: f32,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum BlendPattern {
38 Alpha = 0,
40
41 Additive = 1,
43
44 Multiply = 2,
46
47 Screen = 3,
49
50 Overlay = 4,
52
53 Darken = 5,
55
56 Lighten = 6,
58
59 ColorDodge = 7,
61
62 ColorBurn = 8,
64
65 HardLight = 9,
67
68 SoftLight = 10,
70
71 Difference = 11,
73
74 Exclusion = 12,
76
77 Plasma = 13,
79
80 Snail = 14,
82
83 Triangle = 15,
85
86 Donuts = 16,
88
89 Checkerboard = 17,
91
92 HorizontalStripes = 18,
94
95 VerticalStripes = 19,
97
98 DiagonalStripes = 20,
100
101 Radial = 21,
103
104 Angular = 22,
106
107 PerlinNoise = 23,
109
110 Voronoi = 24,
112
113 Wave = 25,
115
116 RandomPixel = 26,
118}
119
120impl BlendPattern {
121 pub fn all() -> Vec<BlendPattern> {
123 vec![
124 BlendPattern::Alpha,
125 BlendPattern::Additive,
126 BlendPattern::Multiply,
127 BlendPattern::Screen,
128 BlendPattern::Overlay,
129 BlendPattern::Darken,
130 BlendPattern::Lighten,
131 BlendPattern::ColorDodge,
132 BlendPattern::ColorBurn,
133 BlendPattern::HardLight,
134 BlendPattern::SoftLight,
135 BlendPattern::Difference,
136 BlendPattern::Exclusion,
137 BlendPattern::Plasma,
138 BlendPattern::Snail,
139 BlendPattern::Triangle,
140 BlendPattern::Donuts,
141 BlendPattern::Checkerboard,
142 BlendPattern::HorizontalStripes,
143 BlendPattern::VerticalStripes,
144 BlendPattern::DiagonalStripes,
145 BlendPattern::Radial,
146 BlendPattern::Angular,
147 BlendPattern::PerlinNoise,
148 BlendPattern::Voronoi,
149 BlendPattern::Wave,
150 BlendPattern::RandomPixel,
151 ]
152 }
153
154 pub fn name(&self) -> &'static str {
156 match self {
157 BlendPattern::Alpha => "Alpha",
158 BlendPattern::Additive => "Additive",
159 BlendPattern::Multiply => "Multiply",
160 BlendPattern::Screen => "Screen",
161 BlendPattern::Overlay => "Overlay",
162 BlendPattern::Darken => "Darken",
163 BlendPattern::Lighten => "Lighten",
164 BlendPattern::ColorDodge => "Color Dodge",
165 BlendPattern::ColorBurn => "Color Burn",
166 BlendPattern::HardLight => "Hard Light",
167 BlendPattern::SoftLight => "Soft Light",
168 BlendPattern::Difference => "Difference",
169 BlendPattern::Exclusion => "Exclusion",
170 BlendPattern::Plasma => "Plasma",
171 BlendPattern::Snail => "Snail",
172 BlendPattern::Triangle => "Triangle",
173 BlendPattern::Donuts => "Donuts",
174 BlendPattern::Checkerboard => "Checkerboard",
175 BlendPattern::HorizontalStripes => "Horizontal Stripes",
176 BlendPattern::VerticalStripes => "Vertical Stripes",
177 BlendPattern::DiagonalStripes => "Diagonal Stripes",
178 BlendPattern::Radial => "Radial",
179 BlendPattern::Angular => "Angular",
180 BlendPattern::PerlinNoise => "Perlin Noise",
181 BlendPattern::Voronoi => "Voronoi",
182 BlendPattern::Wave => "Wave",
183 BlendPattern::RandomPixel => "Random Pixel",
184 }
185 }
186
187 pub fn from_index(index: usize) -> Option<BlendPattern> {
189 match index {
190 0 => Some(BlendPattern::Alpha),
191 1 => Some(BlendPattern::Additive),
192 2 => Some(BlendPattern::Multiply),
193 3 => Some(BlendPattern::Screen),
194 4 => Some(BlendPattern::Overlay),
195 5 => Some(BlendPattern::Darken),
196 6 => Some(BlendPattern::Lighten),
197 7 => Some(BlendPattern::ColorDodge),
198 8 => Some(BlendPattern::ColorBurn),
199 9 => Some(BlendPattern::HardLight),
200 10 => Some(BlendPattern::SoftLight),
201 11 => Some(BlendPattern::Difference),
202 12 => Some(BlendPattern::Exclusion),
203 13 => Some(BlendPattern::Plasma),
204 14 => Some(BlendPattern::Snail),
205 15 => Some(BlendPattern::Triangle),
206 16 => Some(BlendPattern::Donuts),
207 17 => Some(BlendPattern::Checkerboard),
208 18 => Some(BlendPattern::HorizontalStripes),
209 19 => Some(BlendPattern::VerticalStripes),
210 20 => Some(BlendPattern::DiagonalStripes),
211 21 => Some(BlendPattern::Radial),
212 22 => Some(BlendPattern::Angular),
213 23 => Some(BlendPattern::PerlinNoise),
214 24 => Some(BlendPattern::Voronoi),
215 25 => Some(BlendPattern::Wave),
216 26 => Some(BlendPattern::RandomPixel),
217 _ => None,
218 }
219 }
220}
221
222impl Default for DoublePreset {
223 fn default() -> Self {
224 Self {
225 preset_a: MilkPreset::default(),
226 preset_b: MilkPreset::default(),
227 blend_pattern: BlendPattern::Alpha,
228 blend_amount: 0.5,
229 animate_blend: false,
230 animation_speed: 1.0,
231 }
232 }
233}
234
235impl DoublePreset {
236 pub fn new(preset_a: MilkPreset, preset_b: MilkPreset) -> Self {
238 Self {
239 preset_a,
240 preset_b,
241 blend_pattern: BlendPattern::Alpha,
242 blend_amount: 0.5,
243 animate_blend: false,
244 animation_speed: 1.0,
245 }
246 }
247
248 pub fn with_pattern(mut self, pattern: BlendPattern) -> Self {
250 self.blend_pattern = pattern;
251 self
252 }
253
254 pub fn with_blend_amount(mut self, amount: f32) -> Self {
256 self.blend_amount = amount.clamp(0.0, 1.0);
257 self
258 }
259
260 pub fn with_animation(mut self, speed: f32) -> Self {
262 self.animate_blend = true;
263 self.animation_speed = speed;
264 self
265 }
266}
267
268pub fn parse_double_preset(content: &str) -> Result<DoublePreset> {
270 let mut blend_pattern = BlendPattern::Alpha;
284 let mut blend_amount = 0.5;
285 let mut animate_blend = false;
286 let mut animation_speed = 1.0;
287
288 let mut preset_a_content = String::new();
289 let mut preset_b_content = String::new();
290
291 let mut current_section = "";
292
293 for line in content.lines() {
294 let trimmed = line.trim();
295
296 if trimmed.starts_with('[') && trimmed.ends_with(']') {
297 let section = &trimmed[1..trimmed.len() - 1];
298
299 if section == "DoublePreset" || section == "PresetA" || section == "PresetB" {
301 current_section = section;
302 continue;
303 }
304
305 match current_section {
307 "PresetA" => {
308 preset_a_content.push_str(line);
309 preset_a_content.push('\n');
310 }
311 "PresetB" => {
312 preset_b_content.push_str(line);
313 preset_b_content.push('\n');
314 }
315 _ => {}
316 }
317 continue;
318 }
319
320 match current_section {
321 "DoublePreset" => {
322 if let Some((key, value)) = trimmed.split_once('=') {
323 match key.trim() {
324 "BlendPattern" => {
325 if let Ok(index) = value.trim().parse::<usize>() {
326 blend_pattern =
327 BlendPattern::from_index(index).unwrap_or(BlendPattern::Alpha);
328 }
329 }
330 "BlendAmount" => {
331 if let Ok(amount) = value.trim().parse::<f32>() {
332 blend_amount = amount.clamp(0.0, 1.0);
333 }
334 }
335 "AnimateBlend" => {
336 animate_blend = value.trim() == "1";
337 }
338 "AnimationSpeed" => {
339 if let Ok(speed) = value.trim().parse::<f32>() {
340 animation_speed = speed;
341 }
342 }
343 _ => {}
344 }
345 }
346 }
347 "PresetA" => {
348 preset_a_content.push_str(line);
349 preset_a_content.push('\n');
350 }
351 "PresetB" => {
352 preset_b_content.push_str(line);
353 preset_b_content.push('\n');
354 }
355 _ => {}
356 }
357 }
358
359 let preset_a = crate::parse_preset(&preset_a_content)?;
361 let preset_b = crate::parse_preset(&preset_b_content)?;
362
363 Ok(DoublePreset {
364 preset_a,
365 preset_b,
366 blend_pattern,
367 blend_amount,
368 animate_blend,
369 animation_speed,
370 })
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_blend_pattern_all() {
379 let patterns = BlendPattern::all();
380 assert_eq!(patterns.len(), 27);
381 }
382
383 #[test]
384 fn test_blend_pattern_from_index() {
385 assert_eq!(BlendPattern::from_index(0), Some(BlendPattern::Alpha));
386 assert_eq!(BlendPattern::from_index(13), Some(BlendPattern::Plasma));
387 assert_eq!(
388 BlendPattern::from_index(26),
389 Some(BlendPattern::RandomPixel)
390 );
391 assert_eq!(BlendPattern::from_index(27), None);
392 }
393
394 #[test]
395 fn test_double_preset_creation() {
396 let preset_a = MilkPreset::default();
397 let preset_b = MilkPreset::default();
398
399 let double = DoublePreset::new(preset_a, preset_b)
400 .with_pattern(BlendPattern::Plasma)
401 .with_blend_amount(0.7)
402 .with_animation(2.0);
403
404 assert_eq!(double.blend_pattern, BlendPattern::Plasma);
405 assert_eq!(double.blend_amount, 0.7);
406 assert!(double.animate_blend);
407 assert_eq!(double.animation_speed, 2.0);
408 }
409}