1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
29pub enum BlendMode {
30 #[default]
33 Normal,
34 Multiply,
36 Add,
38 Screen,
40 Subtract,
42 Min,
44 Max,
46 Erase,
48
49 Overlay,
52 HardLight,
54 SoftLight,
56 PinLight,
58 HardMix,
60 VividLight,
62 LinearLight,
64 ColorBurn,
66 ColorDodge,
68 LinearBurn,
70 LinearDodge,
72 Darken,
74 Lighten,
76 Difference,
78 Exclusion,
80 Negation,
82 Divide,
84 Saturation,
86 Color,
88 Luminosity,
90}
91
92impl BlendMode {
93 #[must_use]
97 pub const fn is_advanced(self) -> bool {
98 self.native_blend_state().is_none()
99 }
100
101 #[must_use]
104 pub const fn native_blend_state(self) -> Option<wgpu::BlendState> {
105 use wgpu::{BlendComponent, BlendFactor as F, BlendOperation as Op, BlendState};
106 Some(match self {
107 BlendMode::Normal => BlendState::ALPHA_BLENDING,
108 BlendMode::Multiply => BlendState {
109 color: BlendComponent {
110 src_factor: F::Dst,
111 dst_factor: F::OneMinusSrcAlpha,
112 operation: Op::Add,
113 },
114 alpha: BlendComponent::OVER,
115 },
116 BlendMode::Add => BlendState {
117 color: BlendComponent {
118 src_factor: F::SrcAlpha,
119 dst_factor: F::One,
120 operation: Op::Add,
121 },
122 alpha: BlendComponent::OVER,
123 },
124 BlendMode::Screen => BlendState {
125 color: BlendComponent {
126 src_factor: F::OneMinusDst,
127 dst_factor: F::One,
128 operation: Op::Add,
129 },
130 alpha: BlendComponent::OVER,
131 },
132 BlendMode::Subtract => BlendState {
133 color: BlendComponent {
134 src_factor: F::SrcAlpha,
135 dst_factor: F::One,
136 operation: Op::ReverseSubtract,
137 },
138 alpha: BlendComponent::OVER,
139 },
140 BlendMode::Min => BlendState {
141 color: BlendComponent {
142 src_factor: F::One,
143 dst_factor: F::One,
144 operation: Op::Min,
145 },
146 alpha: BlendComponent::OVER,
147 },
148 BlendMode::Max => BlendState {
149 color: BlendComponent {
150 src_factor: F::One,
151 dst_factor: F::One,
152 operation: Op::Max,
153 },
154 alpha: BlendComponent::OVER,
155 },
156 BlendMode::Erase => BlendState {
157 color: BlendComponent {
158 src_factor: F::Zero,
159 dst_factor: F::OneMinusSrcAlpha,
160 operation: Op::Add,
161 },
162 alpha: BlendComponent {
163 src_factor: F::Zero,
164 dst_factor: F::OneMinusSrcAlpha,
165 operation: Op::Add,
166 },
167 },
168 BlendMode::Overlay
170 | BlendMode::HardLight
171 | BlendMode::SoftLight
172 | BlendMode::PinLight
173 | BlendMode::HardMix
174 | BlendMode::VividLight
175 | BlendMode::LinearLight
176 | BlendMode::ColorBurn
177 | BlendMode::ColorDodge
178 | BlendMode::LinearBurn
179 | BlendMode::LinearDodge
180 | BlendMode::Darken
181 | BlendMode::Lighten
182 | BlendMode::Difference
183 | BlendMode::Exclusion
184 | BlendMode::Negation
185 | BlendMode::Divide
186 | BlendMode::Saturation
187 | BlendMode::Color
188 | BlendMode::Luminosity => return None,
189 })
190 }
191
192 #[must_use]
195 pub const fn css_name(self) -> &'static str {
196 match self {
197 BlendMode::Normal => "normal",
198 BlendMode::Multiply => "multiply",
199 BlendMode::Add => "add",
200 BlendMode::Screen => "screen",
201 BlendMode::Subtract => "subtract",
202 BlendMode::Min => "min",
203 BlendMode::Max => "max",
204 BlendMode::Erase => "erase",
205 BlendMode::Overlay => "overlay",
206 BlendMode::HardLight => "hard-light",
207 BlendMode::SoftLight => "soft-light",
208 BlendMode::PinLight => "pin-light",
209 BlendMode::HardMix => "hard-mix",
210 BlendMode::VividLight => "vivid-light",
211 BlendMode::LinearLight => "linear-light",
212 BlendMode::ColorBurn => "color-burn",
213 BlendMode::ColorDodge => "color-dodge",
214 BlendMode::LinearBurn => "linear-burn",
215 BlendMode::LinearDodge => "linear-dodge",
216 BlendMode::Darken => "darken",
217 BlendMode::Lighten => "lighten",
218 BlendMode::Difference => "difference",
219 BlendMode::Exclusion => "exclusion",
220 BlendMode::Negation => "negation",
221 BlendMode::Divide => "divide",
222 BlendMode::Saturation => "saturation",
223 BlendMode::Color => "color",
224 BlendMode::Luminosity => "luminosity",
225 }
226 }
227
228 #[must_use]
231 pub fn all() -> impl ExactSizeIterator<Item = BlendMode> + DoubleEndedIterator {
232 const ALL: [BlendMode; 28] = [
233 BlendMode::Normal,
234 BlendMode::Multiply,
235 BlendMode::Add,
236 BlendMode::Screen,
237 BlendMode::Subtract,
238 BlendMode::Min,
239 BlendMode::Max,
240 BlendMode::Erase,
241 BlendMode::Overlay,
242 BlendMode::HardLight,
243 BlendMode::SoftLight,
244 BlendMode::PinLight,
245 BlendMode::HardMix,
246 BlendMode::VividLight,
247 BlendMode::LinearLight,
248 BlendMode::ColorBurn,
249 BlendMode::ColorDodge,
250 BlendMode::LinearBurn,
251 BlendMode::LinearDodge,
252 BlendMode::Darken,
253 BlendMode::Lighten,
254 BlendMode::Difference,
255 BlendMode::Exclusion,
256 BlendMode::Negation,
257 BlendMode::Divide,
258 BlendMode::Saturation,
259 BlendMode::Color,
260 BlendMode::Luminosity,
261 ];
262 ALL.into_iter()
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn default_is_normal() {
272 assert_eq!(BlendMode::default(), BlendMode::Normal);
273 }
274
275 #[test]
276 fn standard_set_is_native() {
277 for m in [
278 BlendMode::Normal,
279 BlendMode::Multiply,
280 BlendMode::Add,
281 BlendMode::Screen,
282 BlendMode::Subtract,
283 BlendMode::Min,
284 BlendMode::Max,
285 BlendMode::Erase,
286 ] {
287 assert!(m.native_blend_state().is_some(), "{m:?} should be native");
288 assert!(!m.is_advanced(), "{m:?} should not be advanced");
289 }
290 }
291
292 #[test]
293 fn advanced_set_is_not_native() {
294 for m in [
295 BlendMode::Overlay,
296 BlendMode::HardLight,
297 BlendMode::SoftLight,
298 BlendMode::PinLight,
299 BlendMode::HardMix,
300 BlendMode::VividLight,
301 BlendMode::LinearLight,
302 BlendMode::ColorBurn,
303 BlendMode::ColorDodge,
304 BlendMode::LinearBurn,
305 BlendMode::LinearDodge,
306 BlendMode::Darken,
307 BlendMode::Lighten,
308 BlendMode::Difference,
309 BlendMode::Exclusion,
310 BlendMode::Negation,
311 BlendMode::Divide,
312 BlendMode::Saturation,
313 BlendMode::Color,
314 BlendMode::Luminosity,
315 ] {
316 assert!(m.native_blend_state().is_none(), "{m:?} should be advanced");
317 assert!(m.is_advanced(), "{m:?} should be advanced");
318 }
319 }
320
321 #[test]
322 fn css_names_are_unique_and_kebab() {
323 let mut seen = std::collections::HashSet::new();
324 for m in BlendMode::all() {
325 let n = m.css_name();
326 assert!(seen.insert(n), "duplicate css_name {n}");
327 assert!(
328 n.chars()
329 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
330 "css_name {n} not kebab-case"
331 );
332 }
333 }
334
335 #[test]
336 fn all_iterator_covers_every_variant() {
337 let count = BlendMode::all().count();
338 assert_eq!(count, 28, "BlendMode::all() must enumerate all variants");
339 }
340}