use std::{
cell::RefCell,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{
Color, RenderEffect, RuntimeShader, SubstrateSpec, render_effect::ShaderSpecializationCache,
};
#[derive(Clone, Copy, Debug)]
pub struct LiquidGlassSpecialization {
pub flag: &'static str,
pub slots: &'static [usize],
pub inactive: fn(&[f32]) -> bool,
}
fn slot(uniforms: &[f32], index: usize) -> f32 {
uniforms.get(index).copied().unwrap_or(0.0)
}
pub const LIQUID_GLASS_SPECIALIZATIONS: &[LiquidGlassSpecialization] = &[
LiquidGlassSpecialization {
flag: "GLASS_DIRECTIONAL_REFRACTION_OFF",
slots: &[GLASS_REFRACTION_MODE_UNIFORM],
inactive: |u| slot(u, GLASS_REFRACTION_MODE_UNIFORM) <= 0.5,
},
LiquidGlassSpecialization {
flag: "GLASS_LOUPE_OFF",
slots: &[80],
inactive: |u| slot(u, 80) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_FOLD_OFF",
slots: &[GLASS_FOLD_DEPTH_UNIFORM],
inactive: |u| slot(u, GLASS_FOLD_DEPTH_UNIFORM) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_SCENE_SHAPES_OFF",
slots: &[30],
inactive: |u| slot(u, 30) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_WOBBLE_OFF",
slots: &[32, 26],
inactive: |u| slot(u, 32) == 0.0 && slot(u, 26) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_ELLIPSE_BLEND_OFF",
slots: &[110],
inactive: |u| slot(u, 110) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_PROJECTION_OFF",
slots: &[168, 169],
inactive: |u| {
slot(u, 168) <= 0.0
|| slot(u, 169) <= 0.0
|| (slot(u, 168) == 1.0 && slot(u, 169) == 1.0)
},
},
LiquidGlassSpecialization {
flag: "GLASS_STRAIN_OFF",
slots: &[106, 107, 108, 109],
inactive: |u| {
let axis_identity = (slot(u, 106) == 0.0 && slot(u, 107) == 0.0)
|| (slot(u, 106) == 1.0 && slot(u, 107) == 0.0);
let ratio_identity = slot(u, 108) <= 0.0
|| slot(u, 109) <= 0.0
|| (slot(u, 108) == 1.0 && slot(u, 109) == 1.0);
axis_identity && ratio_identity
},
},
LiquidGlassSpecialization {
flag: "GLASS_ZOOM_ANCHOR_OFF",
slots: &[
GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM + 1,
],
inactive: |u| {
slot(u, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM) == 0.0
&& slot(u, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM + 1) == 0.0
},
},
LiquidGlassSpecialization {
flag: "GLASS_TOUCH_OFF",
slots: &[120],
inactive: |u| slot(u, 120) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_CONTENT_MASK_OFF",
slots: &[112],
inactive: |u| slot(u, 112) <= 0.5,
},
LiquidGlassSpecialization {
flag: "GLASS_OPTICAL_BLUR_OFF",
slots: &[GLASS_BLUR_RADIUS_UNIFORM],
inactive: |u| slot(u, GLASS_BLUR_RADIUS_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_SHADOW_OFF",
slots: &[102],
inactive: |u| slot(u, 102) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_ZOOM_OFF",
slots: &[GLASS_OPTICAL_ZOOM_UNIFORM],
inactive: |u| slot(u, GLASS_OPTICAL_ZOOM_UNIFORM) <= 1.0,
},
LiquidGlassSpecialization {
flag: GLASS_PHYSICAL_REFRACTION_OFF_FLAG,
slots: &[GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM],
inactive: |u| slot(u, GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM) <= 0.5,
},
LiquidGlassSpecialization {
flag: GLASS_DISPERSION_OFF_FLAG,
slots: &[GLASS_DISPERSION_UNIFORM],
inactive: |u| slot(u, GLASS_DISPERSION_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_ADAPTIVE_FROST_OFF",
slots: &[GLASS_ADAPTIVE_FROST_UNIFORM],
inactive: |u| slot(u, GLASS_ADAPTIVE_FROST_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_INK_OFF",
slots: &[127],
inactive: |u| slot(u, 127) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_RIM_STYLE_OFF",
slots: &[GLASS_RIM_STYLE_UNIFORM],
inactive: |u| slot(u, GLASS_RIM_STYLE_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_KEY_FILL_OFF",
slots: &[GLASS_KEY_FILL_UNIFORM],
inactive: |u| slot(u, GLASS_KEY_FILL_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_SEPARATE_CONTENT_OFF",
slots: &[GLASS_FOREGROUND_CONTENT_UNIFORM],
inactive: |u| slot(u, GLASS_FOREGROUND_CONTENT_UNIFORM) <= 0.5,
},
LiquidGlassSpecialization {
flag: "GLASS_TONE_CURVE_OFF",
slots: &[GLASS_ADAPTIVE_TONE_UNIFORM],
inactive: |u| slot(u, GLASS_ADAPTIVE_TONE_UNIFORM) <= 0.5,
},
LiquidGlassSpecialization {
flag: "GLASS_PANE_OFF",
slots: &[GLASS_PANE_BLEND_UNIFORM],
inactive: |u| slot(u, GLASS_PANE_BLEND_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_INSET_SHADOW_OFF",
slots: &[GLASS_INNER_SHADOW_PRESENCE_UNIFORM],
inactive: |u| {
slot(u, GLASS_INNER_SHADOW_PRESENCE_UNIFORM) <= 0.0
|| slot(u, GLASS_INNER_SHADOW_UNIFORM + 3) <= 0.0
},
},
LiquidGlassSpecialization {
flag: "GLASS_HOLDING_OFF",
slots: &[GLASS_FACE_RESPONSE_UNIFORM],
inactive: |u| slot(u, GLASS_FACE_RESPONSE_UNIFORM) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_PARTIAL_ACTIVITY_OFF",
slots: &[GLASS_ACTIVITY_UNIFORM],
inactive: |u| slot(u, GLASS_ACTIVITY_UNIFORM) >= 1.0,
},
LiquidGlassSpecialization {
flag: "GLASS_CAPSULE_SMOOTHING_OFF",
slots: &[GLASS_CAPSULE_SMOOTHING_UNIFORM],
inactive: |u| slot(u, GLASS_CAPSULE_SMOOTHING_UNIFORM) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_INTERIOR_GUARD",
slots: &[],
inactive: |_| true,
},
];
const GLASS_INNER_SHADOW_PRESENCE_UNIFORM: usize = GLASS_INNER_SHADOW_UNIFORM + 7;
pub fn glass_material_folds_enabled() -> bool {
GLASS_MATERIAL_FOLDS.load(Ordering::Relaxed)
}
pub fn set_glass_material_folds(enabled: bool) {
GLASS_MATERIAL_FOLDS.store(enabled, Ordering::Relaxed);
}
static GLASS_MATERIAL_FOLDS: AtomicBool = AtomicBool::new(cfg!(target_os = "android"));
pub fn specialize_liquid_glass(shader: &mut RuntimeShader) {
specialize_liquid_glass_with_folds(shader, glass_material_folds_enabled());
}
pub fn specialize_liquid_glass_with_folds(shader: &mut RuntimeShader, folds: bool) {
const _: () = assert!(LIQUID_GLASS_SPECIALIZATIONS.len() <= u32::BITS as usize);
const CACHE_CAPACITY: usize = 32;
type SpecializationKey = (
u32,
Option<u32>,
bool,
bool,
bool,
Option<u32>,
u8,
Option<u32>,
);
thread_local! {
static CACHE: RefCell<ShaderSpecializationCache<SpecializationKey, CACHE_CAPACITY>> =
const { RefCell::new(ShaderSpecializationCache::new()) };
}
let uniforms = shader.uniforms();
let flags = if folds {
LIQUID_GLASS_SPECIALIZATIONS
.iter()
.enumerate()
.fold(0, |flags, (index, specialization)| {
flags | (u32::from((specialization.inactive)(uniforms)) << index)
})
} else {
0
};
let substrate_radius = (slot(uniforms, GLASS_ADAPTIVE_FROST_UNIFORM) > 0.0).then(|| {
(GLASS_ADAPTIVE_NEIGHBOURHOOD_DP * slot(uniforms, GLASS_EFFECT_DENSITY_UNIFORM).max(1.0))
.to_bits()
});
let mean_tone = slot(uniforms, GLASS_ADAPTIVE_TONE_UNIFORM) > 0.5;
let pane_radius = (slot(uniforms, GLASS_PANE_BLEND_UNIFORM) > 0.0)
.then(|| slot(uniforms, GLASS_PANE_BLEND_UNIFORM).to_bits());
let projection = [
slot(uniforms, GLASS_OPTICAL_PROJECTION_UNIFORM),
slot(uniforms, GLASS_OPTICAL_PROJECTION_UNIFORM + 1),
];
let projected = projection.iter().all(|v| *v > 0.0) && projection != [1.0, 1.0];
let stage = slot(uniforms, GLASS_OPTICAL_STAGE_UNIFORM) as u8;
let backdrop_radius = (stage == 2 && slot(uniforms, GLASS_BACKDROP_BLUR_UNIFORM) > 0.0)
.then(|| slot(uniforms, GLASS_BACKDROP_BLUR_UNIFORM).to_bits());
shader.set_preserves_transparency(slot(uniforms, 112) > 0.5);
CACHE.with_borrow_mut(|cache| {
cache.apply(
shader,
(
flags,
substrate_radius,
folds,
mean_tone,
projected,
pane_radius,
stage,
backdrop_radius,
),
|shader, &(flags, radius, folds, mean_tone, projected, pane_radius, stage, backdrop_radius)| {
for (index, specialization) in LIQUID_GLASS_SPECIALIZATIONS.iter().enumerate() {
if flags & (1 << index) != 0 {
shader.set_override(specialization.flag, 1.0);
} else {
shader.clear_override(specialization.flag);
}
}
shader.set_draw_split((folds && !projected).then_some(GLASS_RIM_DRAW_OVERRIDE));
if folds {
shader.set_override(GLASS_OPTICAL_STAGE_OVERRIDE, f64::from(stage));
} else {
shader.clear_override(GLASS_OPTICAL_STAGE_OVERRIDE);
}
shader.set_specialization_exact(true);
let substrate = radius.map(|radius| SubstrateSpec::Blur {
radius_px: f32::from_bits(radius),
});
let mut substrates = arrayvec::ArrayVec::<SubstrateSpec, 3>::new();
substrates.extend(substrate);
if mean_tone {
substrates.push(SubstrateSpec::Mean);
}
if let Some(radius) = pane_radius {
substrates.push(SubstrateSpec::Blur {
radius_px: f32::from_bits(radius),
});
}
if matches!(stage, 1 | 2) {
substrates.clear();
if let Some(radius) = backdrop_radius {
substrates.push(SubstrateSpec::Blur { radius_px: f32::from_bits(radius) });
}
}
shader.set_substrates(&substrates);
},
);
});
}
pub const GLASS_RIM_DRAW_OVERRIDE: &str = "GLASS_RIM_DRAW";
pub const GLASS_OPTICAL_STAGE_OVERRIDE: &str = "GLASS_OPTICAL_STAGE";
pub const GLASS_DISPERSION_OFF_FLAG: &str = "GLASS_DISPERSION_OFF";
pub const GLASS_PHYSICAL_REFRACTION_OFF_FLAG: &str = "GLASS_PHYSICAL_REFRACTION_OFF";
pub const GLASS_ADAPTIVE_NEIGHBOURHOOD_DP: f32 = 16.0;
pub fn liquid_glass_runtime_effect(shader: RuntimeShader) -> RenderEffect {
if slot(shader.uniforms(), GLASS_REFRACTION_MODE_UNIFORM) >= 1.5
&& slot(shader.uniforms(), 112) <= 0.5
&& slot(shader.uniforms(), GLASS_OPTICAL_STAGE_UNIFORM) == 0.0
{
let stage = |index: u8| {
let mut pass = shader.clone();
pass.set_float(GLASS_OPTICAL_STAGE_UNIFORM, f32::from(index));
if index < 3 {
pass.set_output_support(None);
pass.set_output_padding(0.0);
}
glass_shader_effect(pass)
};
stage(1).then(stage(2)).then(stage(3))
} else {
glass_shader_effect(shader)
}
}
fn glass_shader_effect(mut shader: RuntimeShader) -> RenderEffect {
specialize_liquid_glass(&mut shader);
if matches!(
slot(shader.uniforms(), GLASS_OPTICAL_STAGE_UNIFORM),
1.0 | 2.0
) {
shader.set_draw_split(None);
}
shader.set_batched_source(true);
RenderEffect::runtime_shader(shader)
}
pub const GLASS_ADAPTIVE_FROST_UNIFORM: usize = 91;
pub const GLASS_BLUR_RADIUS_UNIFORM: usize = 93;
pub const GLASS_REFRACTION_CURVE_UNIFORM: usize = 94;
pub const GLASS_DISPERSION_UNIFORM: usize = 95;
pub const GLASS_TRANSMISSION_REFRACTION_UNIFORM: usize = 96;
pub const GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM: usize = 98;
pub const GLASS_EFFECT_DENSITY_UNIFORM: usize = 99;
pub const GLASS_MENISCUS_ABSORPTION_UNIFORM: usize = 100;
pub const GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM: usize = 101;
pub const GLASS_FOLD_DEPTH_UNIFORM: usize = 88;
pub const GLASS_OPTICAL_ZOOM_UNIFORM: usize = 89;
pub const GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM: usize = 128;
pub const GLASS_REFRACTION_MODE_UNIFORM: usize = 130;
pub const GLASS_EDGE_REFRACTION_REACH_UNIFORM: usize = 131;
pub const GLASS_FACE_LIGHTING_OFF_UNIFORM: usize = 132;
pub const GLASS_TOUCH_RADIUS_UNIFORM: usize = 133;
pub const GLASS_LAYER_CLIPPED_UNIFORM: usize = 134;
pub const GLASS_KEY_FILL_UNIFORM: usize = 135;
pub const GLASS_FACE_RESPONSE_UNIFORM: usize = 142;
pub const GLASS_CAPSULE_SMOOTHING_UNIFORM: usize = 146;
pub const GLASS_OPTICAL_STAGE_UNIFORM: usize = 147;
pub const GLASS_EDGE_SPECTRUM_UNIFORM: usize = 148;
pub const GLASS_INNER_SHADOW_UNIFORM: usize = 156;
pub const GLASS_EDGE_RETURN_DEPTH_UNIFORM: usize = 164;
pub const GLASS_OPTICAL_PROJECTION_UNIFORM: usize = 168;
pub const GLASS_ADAPTIVE_TONE_UNIFORM: usize = 166;
pub const GLASS_PANE_BLEND_UNIFORM: usize = 170;
pub const GLASS_BACKDROP_BLUR_UNIFORM: usize = 172;
pub const GLASS_FOREGROUND_CONTENT_UNIFORM: usize = 174;
pub const GLASS_RIM_STYLE_UNIFORM: usize = 28;
pub const GLASS_ACTIVITY_UNIFORM: usize = 111;
pub const GLASS_RESTING_TINT_UNIFORM: usize = 113;
pub const LIQUID_GLASS_WGSL: &str = concat!(
include_str!("../shaders/glass_geometry.wgsl"),
include_str!("../shaders/liquid_glass.wgsl"),
);
pub const LIQUID_GLASS_GEOMETRY_WGSL: &str = include_str!("../shaders/glass_geometry.wgsl");
pub const GLASS_LIGHT_DIRECTION_UNIFORM: usize = 122;
#[derive(Clone, Debug, PartialEq)]
pub struct LiquidGlassSpec {
pub corner_radius: f32,
pub refraction_depth: f32,
pub refraction_curve: f32,
pub blur_radius: f32,
pub highlight: f32,
pub saturation: f32,
pub lift: f32,
pub contrast: f32,
pub meniscus_absorption: f32,
pub dither: f32,
}
impl Default for LiquidGlassSpec {
fn default() -> Self {
Self {
corner_radius: 28.0,
refraction_depth: 0.34,
refraction_curve: 0.25,
blur_radius: 0.0,
highlight: 0.7,
saturation: 1.0,
lift: 0.0,
contrast: 1.0,
meniscus_absorption: 1.0,
dither: 0.5,
}
}
}
#[derive(Clone, Debug)]
pub struct LiquidGlassRect {
pub left: f32,
pub top: f32,
pub width: f32,
pub height: f32,
pub tint_color: Color,
}
pub fn liquid_glass_effect(
rect: &LiquidGlassRect,
spec: &LiquidGlassSpec,
area_width: f32,
area_height: f32,
) -> RenderEffect {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
let cx = rect.left + rect.width * 0.5;
let cy = rect.top + rect.height * 0.5;
shader.set_float2(0, area_width, area_height);
shader.set_float2(2, cx, cy);
shader.set_float2(4, rect.width, rect.height);
shader.set_float(6, spec.corner_radius);
shader.set_float(9, spec.refraction_depth.clamp(0.0, 2.0));
shader.set_float(
GLASS_REFRACTION_CURVE_UNIFORM,
spec.refraction_curve.clamp(0.05, 1.0),
);
shader.set_float(11, spec.highlight);
shader.set_float4(
14,
rect.tint_color.r(),
rect.tint_color.g(),
rect.tint_color.b(),
rect.tint_color.a(),
);
shader.set_float(18, spec.saturation);
shader.set_float(20, spec.lift);
shader.set_float(21, spec.dither);
shader.set_float(24, spec.contrast);
shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, spec.blur_radius.max(0.0));
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, 1.0);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 1.0);
shader.set_float(
GLASS_MENISCUS_ABSORPTION_UNIFORM,
spec.meniscus_absorption.clamp(0.0, 1.0),
);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_input_padding(liquid_glass_input_padding(spec));
liquid_glass_runtime_effect(shader)
}
fn liquid_glass_input_padding(spec: &LiquidGlassSpec) -> f32 {
spec.blur_radius.max(2.0).ceil()
}
#[derive(Clone, Debug, PartialEq)]
pub struct LiquidLoupeSpec {
pub magnification: f32,
pub focus_offset: (f32, f32),
pub dispersion: f32,
pub highlight: f32,
pub activity: f32,
pub corner_radius: f32,
}
impl Default for LiquidLoupeSpec {
fn default() -> Self {
Self {
magnification: 1.25,
focus_offset: (0.0, 75.0),
dispersion: 0.36,
highlight: 0.42,
activity: 1.0,
corner_radius: 0.0,
}
}
}
pub fn liquid_loupe_effect(node_size: (f32, f32), spec: &LiquidLoupeSpec) -> RenderEffect {
let (w, h) = (node_size.0.max(1.0), node_size.1.max(1.0));
let activity = spec.activity.clamp(0.0, 1.0);
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float2(0, w, h);
shader.set_float2(2, w * 0.5, h * 0.5);
shader.set_float2(4, w, h);
if spec.corner_radius > 0.0 {
shader.set_float(6, spec.corner_radius.min(0.5 * h.min(w)));
} else {
shader.set_float(6, -1.0);
}
shader.set_float(9, 0.34 * activity);
shader.set_float(GLASS_REFRACTION_CURVE_UNIFORM, 0.25);
shader.set_float(
GLASS_DISPERSION_UNIFORM,
spec.dispersion.clamp(0.0, 1.0) * activity,
);
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, 1.0);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 1.0);
shader.set_float(GLASS_MENISCUS_ABSORPTION_UNIFORM, 1.0);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_float(11, spec.highlight * activity);
shader.set_float4(14, 1.0, 1.0, 1.0, 0.0);
shader.set_float(18, 1.0);
shader.set_float(20, 0.0);
shader.set_float(21, 0.5);
shader.set_float(24, 1.0);
shader.set_float(28, activity);
shader.set_float(80, 1.0);
shader.set_float2(81, spec.focus_offset.0, spec.focus_offset.1);
shader.set_float(83, 1.0 + (spec.magnification.max(0.2) - 1.0) * activity);
shader.set_float(90, activity);
let focus_reach = (spec.focus_offset.0.powi(2) + spec.focus_offset.1.powi(2)).sqrt();
shader.set_input_padding((focus_reach + 8.0).ceil());
liquid_glass_runtime_effect(shader)
}
pub fn liquid_menu_glass_effect(
node_size: (f32, f32),
blur_radius_px: f32,
progress: f32,
) -> RenderEffect {
let (w, h) = (node_size.0.max(1.0), node_size.1.max(1.0));
let p = progress.clamp(0.0, 1.0);
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float2(0, w, h);
shader.set_float2(2, w * 0.5, h * 0.5);
shader.set_float2(4, w, h);
shader.set_float(6, -1.0);
shader.set_float(9, 0.10 * p);
shader.set_float(GLASS_REFRACTION_CURVE_UNIFORM, 0.25);
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, 1.0);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 1.0);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_float(11, 0.19 * p);
shader.set_float4(14, 0.0, 0.0, 0.0, 0.04 * p);
shader.set_float(18, 1.0 + 0.10 * p);
shader.set_float(20, -0.06 * p);
shader.set_float(24, 1.0 + 0.05 * p);
shader.set_float(21, 0.5);
let requested_blur = if blur_radius_px > 0.5 {
blur_radius_px * (1.0 - 0.4 * p)
} else {
0.0
};
const WCKSRD_OPTICAL_BLUR_RADIUS_PX: f32 = 2.0;
let (wcksrd_blur, gaussian_blur) = if requested_blur > WCKSRD_OPTICAL_BLUR_RADIUS_PX {
(0.0, requested_blur)
} else {
(requested_blur, 0.0)
};
shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, wcksrd_blur);
shader.set_input_padding(12.0 + requested_blur);
let optical = liquid_glass_runtime_effect(shader);
if gaussian_blur > f32::EPSILON {
RenderEffect::blur_with_edge_treatment(gaussian_blur, crate::TileMode::Mirror).then(optical)
} else {
optical
}
}
pub fn liquid_glass_effect_multi(
rects: &[LiquidGlassRect],
spec: &LiquidGlassSpec,
area_width: f32,
area_height: f32,
) -> Option<RenderEffect> {
let mut result: Option<RenderEffect> = None;
for rect in rects {
let effect = liquid_glass_effect(rect, spec, area_width, area_height);
result = Some(match result {
Some(existing) => existing.then(effect),
None => effect,
});
}
result
}
#[cfg(test)]
#[path = "tests/liquid_glass_tests.rs"]
mod tests;