Skip to main content

argui_paint/
effect.rs

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}