1use argui_core::{Color, Point, Rect, Size};
2
3use crate::CornerRadii;
4
5#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
6pub enum ProfileDomain {
7 Ui,
8 Overlay,
9 Engine,
10}
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
13pub struct RenderObjectId {
14 pub domain: ProfileDomain,
15 pub value: u64,
16}
17
18impl RenderObjectId {
19 #[must_use]
20 pub const fn new(domain: ProfileDomain, value: u64) -> Self {
21 Self { domain, value }
22 }
23}
24
25#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
26pub struct EffectId(pub &'static str);
27
28impl EffectId {
29 #[must_use]
30 pub const fn new(namespaced_name: &'static str) -> Self {
31 Self(namespaced_name)
32 }
33}
34
35#[derive(Clone, Debug, PartialEq)]
36pub enum EffectValue {
37 F32(f32),
38 I32(i32),
39 U32(u32),
40 Bool(bool),
41 Vec2([f32; 2]),
42 Vec3([f32; 3]),
43 Vec4([f32; 4]),
44 Mat3([f32; 9]),
45 Mat4([f32; 16]),
46 Color(Color),
47 LogicalPixels(f32),
48}
49
50impl EffectValue {
51 #[must_use]
52 pub fn scaled(&self, factor: f32) -> Self {
53 match self {
54 Self::LogicalPixels(value) => Self::LogicalPixels(value * factor),
55 value => value.clone(),
56 }
57 }
58
59 pub fn write_words(&self, words: &mut Vec<u32>) {
60 match self {
61 Self::F32(value) | Self::LogicalPixels(value) => words.push(value.to_bits()),
62 Self::I32(value) => words.push(*value as u32),
63 Self::U32(value) => words.push(*value),
64 Self::Bool(value) => words.push(u32::from(*value)),
65 Self::Vec2(values) => write_f32_words(words, values),
66 Self::Vec3(values) => write_f32_words(words, values),
67 Self::Vec4(values) => write_f32_words(words, values),
68 Self::Color(value) => write_f32_words(words, &value.to_linear_rgba()),
69 Self::Mat3(values) => write_f32_words(words, values),
70 Self::Mat4(values) => write_f32_words(words, values),
71 }
72 }
73}
74
75fn write_f32_words(words: &mut Vec<u32>, values: &[f32]) {
76 words.extend(values.iter().map(|value| value.to_bits()));
77}
78
79#[derive(Clone, Debug, PartialEq)]
80pub struct EffectArgument {
81 pub name: &'static str,
82 pub value: EffectValue,
83}
84
85impl EffectArgument {
86 #[must_use]
87 pub const fn new(name: &'static str, value: EffectValue) -> Self {
88 Self { name, value }
89 }
90}
91
92impl From<(&'static str, EffectValue)> for EffectArgument {
93 fn from((name, value): (&'static str, EffectValue)) -> Self {
94 Self::new(name, value)
95 }
96}
97
98#[derive(Clone, Debug, PartialEq)]
99pub struct EffectInstance {
100 pub id: EffectId,
101 pub parameters: Vec<EffectArgument>,
102 pub expansion: f32,
103}
104
105impl EffectInstance {
106 #[must_use]
107 pub fn new<I, A>(id: EffectId, parameters: I) -> Self
108 where
109 I: IntoIterator<Item = A>,
110 A: Into<EffectArgument>,
111 {
112 Self {
113 id,
114 parameters: parameters.into_iter().map(Into::into).collect(),
115 expansion: 0.0,
116 }
117 }
118
119 #[must_use]
120 pub fn expansion(mut self, pixels: f32) -> Self {
121 self.expansion = pixels.max(0.0);
122 self
123 }
124
125 #[must_use]
126 pub fn packed_words(&self) -> Vec<u32> {
127 let mut words = Vec::new();
128 for parameter in &self.parameters {
129 parameter.value.write_words(&mut words);
130 }
131 words
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq)]
136pub struct Refraction {
137 pub strength: f32,
138 pub chromatic_aberration: f32,
139 pub edge: f32,
140}
141
142impl Refraction {
143 #[must_use]
144 pub const fn new(strength: f32) -> Self {
145 Self {
146 strength,
147 chromatic_aberration: 0.0,
148 edge: 0.15,
149 }
150 }
151
152 #[must_use]
153 pub const fn chromatic_aberration(mut self, amount: f32) -> Self {
154 self.chromatic_aberration = amount;
155 self
156 }
157}
158
159#[derive(Clone, Debug, PartialEq)]
160pub enum Filter {
161 Blur(f32),
162 Brightness(f32),
163 Contrast(f32),
164 Saturation(f32),
165 HueRotate(f32),
166 Opacity(f32),
167 ColorMatrix([f32; 20]),
168 Refraction(Refraction),
169 Effect(EffectInstance),
170}
171
172impl Filter {
173 #[must_use]
174 pub fn expansion(&self) -> f32 {
175 match self {
176 Self::Blur(radius) => radius.max(0.0) * 3.0,
177 Self::Effect(effect) => effect.expansion,
178 _ => 0.0,
179 }
180 }
181
182 #[must_use]
183 pub fn scaled(&self, factor: f32) -> Self {
184 match self {
185 Self::Blur(radius) => Self::Blur(radius * factor),
186 Self::Effect(effect) => Self::Effect(EffectInstance {
187 id: effect.id,
188 parameters: effect
189 .parameters
190 .iter()
191 .map(|argument| EffectArgument {
192 name: argument.name,
193 value: argument.value.scaled(factor),
194 })
195 .collect(),
196 expansion: effect.expansion * factor,
197 }),
198 other => other.clone(),
199 }
200 }
201}
202
203#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
204pub enum BlendMode {
205 #[default]
206 Normal,
207 Multiply,
208 Screen,
209 Overlay,
210 Darken,
211 Lighten,
212 Difference,
213 Exclusion,
214 PlusLighter,
215}
216
217#[derive(Clone, Copy, Debug, PartialEq)]
218pub struct Shadow {
219 pub offset: [f32; 2],
220 pub blur: f32,
221 pub spread: f32,
222 pub color: Color,
223 pub inset: bool,
224}
225
226impl Shadow {
227 #[must_use]
228 pub const fn drop(offset: [f32; 2], blur: f32, color: Color) -> Self {
229 Self {
230 offset,
231 blur,
232 spread: 0.0,
233 color,
234 inset: false,
235 }
236 }
237
238 #[must_use]
239 pub const fn glow(blur: f32, color: Color) -> Self {
240 Self::drop([0.0, 0.0], blur, color)
241 }
242
243 #[must_use]
244 pub const fn blur(mut self, radius: f32) -> Self {
245 self.blur = radius;
246 self
247 }
248
249 #[must_use]
250 pub const fn spread(mut self, radius: f32) -> Self {
251 self.spread = radius;
252 self
253 }
254
255 #[must_use]
256 pub const fn inset(mut self, inset: bool) -> Self {
257 self.inset = inset;
258 self
259 }
260}
261
262#[derive(Clone, Copy, Debug, Default, PartialEq)]
263pub enum LayerMask {
264 #[default]
265 None,
266 Bounds,
267 Rounded(CornerRadii),
268}
269
270#[derive(Clone, Debug, PartialEq)]
271pub struct LayerStyle {
272 pub bounds: Rect,
273 pub opacity: f32,
274 pub blend_mode: BlendMode,
275 pub filters: Vec<Filter>,
276 pub backdrop_filters: Vec<Filter>,
277 pub shadows: Vec<Shadow>,
278 pub mask: LayerMask,
279 pub profile: Option<RenderObjectId>,
280}
281
282impl LayerStyle {
283 #[must_use]
284 pub const fn new(bounds: Rect) -> Self {
285 Self {
286 bounds,
287 opacity: 1.0,
288 blend_mode: BlendMode::Normal,
289 filters: Vec::new(),
290 backdrop_filters: Vec::new(),
291 shadows: Vec::new(),
292 mask: LayerMask::None,
293 profile: None,
294 }
295 }
296
297 #[must_use]
298 pub fn filter(mut self, filter: Filter) -> Self {
299 self.filters.push(filter);
300 self
301 }
302
303 #[must_use]
304 pub fn backdrop(mut self, filter: Filter) -> Self {
305 self.backdrop_filters.push(filter);
306 self
307 }
308
309 #[must_use]
310 pub fn shadow(mut self, shadow: Shadow) -> Self {
311 self.shadows.push(shadow);
312 self
313 }
314
315 #[must_use]
316 pub const fn opacity(mut self, opacity: f32) -> Self {
317 self.opacity = opacity;
318 self
319 }
320
321 #[must_use]
322 pub const fn blend(mut self, blend_mode: BlendMode) -> Self {
323 self.blend_mode = blend_mode;
324 self
325 }
326
327 #[must_use]
328 pub const fn mask(mut self, mask: LayerMask) -> Self {
329 self.mask = mask;
330 self
331 }
332
333 #[must_use]
334 pub const fn profile(mut self, profile: RenderObjectId) -> Self {
335 self.profile = Some(profile);
336 self
337 }
338
339 #[must_use]
340 pub fn requires_offscreen(&self) -> bool {
341 self.opacity != 1.0
342 || self.blend_mode != BlendMode::Normal
343 || !self.filters.is_empty()
344 || !self.backdrop_filters.is_empty()
345 || !self.shadows.is_empty()
346 || self.mask != LayerMask::None
347 }
348
349 #[must_use]
350 pub fn expanded_bounds(&self) -> Rect {
351 let mut expansion = self.foreground_expansion();
352 for shadow in &self.shadows {
353 expansion = expansion.max(
354 shadow.blur.max(0.0) * 3.0
355 + shadow.spread.max(0.0)
356 + shadow.offset[0].abs().max(shadow.offset[1].abs()),
357 );
358 }
359 Rect::new(
360 Point::new(
361 self.bounds.origin.x - expansion,
362 self.bounds.origin.y - expansion,
363 ),
364 Size::new(
365 self.bounds.size.width + expansion * 2.0,
366 self.bounds.size.height + expansion * 2.0,
367 ),
368 )
369 }
370
371 #[must_use]
372 pub fn foreground_expansion(&self) -> f32 {
373 self.filters.iter().map(Filter::expansion).sum()
374 }
375
376 #[must_use]
377 pub fn foreground_bounds(&self) -> Rect {
378 outset(self.bounds, self.foreground_expansion())
379 }
380
381 #[must_use]
382 pub fn scaled(&self, factor: f32) -> Self {
383 let mut scaled = self.clone();
384 scaled.bounds = Rect::new(
385 Point::new(self.bounds.origin.x * factor, self.bounds.origin.y * factor),
386 Size::new(
387 self.bounds.size.width * factor,
388 self.bounds.size.height * factor,
389 ),
390 );
391 scaled.filters = self
392 .filters
393 .iter()
394 .map(|filter| filter.scaled(factor))
395 .collect();
396 scaled.backdrop_filters = self
397 .backdrop_filters
398 .iter()
399 .map(|filter| filter.scaled(factor))
400 .collect();
401 for shadow in &mut scaled.shadows {
402 shadow.offset[0] *= factor;
403 shadow.offset[1] *= factor;
404 shadow.blur *= factor;
405 shadow.spread *= factor;
406 }
407 if let LayerMask::Rounded(radii) = scaled.mask {
408 scaled.mask = LayerMask::Rounded(radii.scaled(factor));
409 }
410 scaled
411 }
412}
413
414fn outset(rect: Rect, amount: f32) -> Rect {
415 Rect::new(
416 Point::new(rect.origin.x - amount, rect.origin.y - amount),
417 Size::new(
418 rect.size.width + amount * 2.0,
419 rect.size.height + amount * 2.0,
420 ),
421 )
422}