ailloli_ui_runtime/scene/isolated_effects.rs
1//! Per-layer isolated compositing effects (isolated compositor).
2
3/// Blend mode when compositing an isolated pass into the main framebuffer.
4///
5/// The renderer applies these modes to linear or encoded color according to its
6/// backend contract; this enum carries intent and performs no blending itself.
7///
8/// # Examples
9///
10/// ```
11/// use ailloli_ui_runtime::BlendMode;
12/// assert_eq!(BlendMode::default(), BlendMode::Normal);
13/// assert_ne!(BlendMode::Multiply, BlendMode::Screen);
14/// ```
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum BlendMode {
17 /// Ordinary source-over compositing; the default.
18 #[default]
19 Normal,
20 /// Multiply source and backdrop colors.
21 Multiply,
22 /// Screen source and backdrop colors.
23 Screen,
24}
25
26/// Post-effects applied to an isolated offscreen pass before compositing.
27///
28/// Values are stored without clamping. Render backends decide how to handle
29/// opacity outside `0.0..=1.0`, non-finite values, and blur radii larger than
30/// their resource limits. [`Self::is_noop`] uses an opacity tolerance of 0.001
31/// and treats non-positive blur radii as disabled.
32///
33/// # Examples
34///
35/// ```
36/// use ailloli_ui_runtime::{BlendMode, IsolatedEffects};
37/// let effects = IsolatedEffects::default();
38/// assert_eq!(effects.opacity, 1.0);
39/// assert_eq!(effects.blend_mode, BlendMode::Normal);
40/// assert!(effects.is_noop());
41/// ```
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub struct IsolatedEffects {
44 /// Group opacity multiplier (`1.0` = opaque group).
45 pub opacity: f32,
46 /// Gaussian-ish blur radius in physical pixels (`0.0` = disabled) on layer content.
47 pub blur_radius_px: f32,
48 /// Blur of content already rendered behind this layer (backdrop filter backdrop).
49 pub backdrop_blur_radius_px: f32,
50 /// Blend operation used when recombining the isolated pass.
51 pub blend_mode: BlendMode,
52}
53
54/// Implements the Default contract for IsolatedEffects.
55impl Default for IsolatedEffects {
56 /// Constructs the documented default value.
57 fn default() -> Self {
58 Self {
59 opacity: 1.0,
60 blur_radius_px: 0.0,
61 backdrop_blur_radius_px: 0.0,
62 blend_mode: BlendMode::Normal,
63 }
64 }
65}
66
67/// Provides the operations defined for IsolatedEffects.
68impl IsolatedEffects {
69 /// No offscreen pass required: render in the main single pass.
70 ///
71 /// This returns `true` exactly when opacity is at least `0.999`, both blur
72 /// radii are at most zero, and blend mode is [`BlendMode::Normal`]. Thus
73 /// opacity above one and negative blur values are still considered no-op;
74 /// NaN in any numeric field forces `false` through ordered comparisons.
75 ///
76 /// # Examples
77 ///
78 /// ```
79 /// use ailloli_ui_runtime::IsolatedEffects;
80 /// let mut effects = IsolatedEffects::default();
81 /// effects.opacity = 0.998;
82 /// assert!(!effects.is_noop());
83 /// effects.opacity = 1.0;
84 /// effects.blur_radius_px = -1.0;
85 /// assert!(effects.is_noop());
86 /// ```
87 pub fn is_noop(&self) -> bool {
88 self.opacity >= 0.999
89 && self.blur_radius_px <= 0.0
90 && self.backdrop_blur_radius_px <= 0.0
91 && self.blend_mode == BlendMode::Normal
92 }
93
94 /// Whether this layer must be rendered to an offscreen target.
95 ///
96 /// This is exactly the negation of [`Self::is_noop`]; it does not check
97 /// backend support or allocate a target.
98 ///
99 /// # Examples
100 ///
101 /// ```
102 /// use ailloli_ui_runtime::{BlendMode, IsolatedEffects};
103 /// let effects = IsolatedEffects { blend_mode: BlendMode::Multiply, ..IsolatedEffects::default() };
104 /// assert!(effects.needs_offscreen());
105 /// ```
106 pub fn needs_offscreen(&self) -> bool {
107 !self.is_noop()
108 }
109}