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}