use std::{
cell::{Cell, RefCell},
sync::OnceLock,
};
use cranpose_ui::{Modifier, current_density};
use cranpose_ui_graphics::{
Color, GLASS_ACTIVITY_UNIFORM, GLASS_ADAPTIVE_FROST_UNIFORM, GLASS_ADAPTIVE_TONE_UNIFORM,
GLASS_BLUR_RADIUS_UNIFORM, GLASS_DISPERSION_UNIFORM, GLASS_EDGE_REFRACTION_REACH_UNIFORM,
GLASS_EDGE_RETURN_DEPTH_UNIFORM, GLASS_EDGE_SPECTRUM_UNIFORM, GLASS_EFFECT_DENSITY_UNIFORM,
GLASS_FACE_LIGHTING_OFF_UNIFORM, GLASS_FACE_RESPONSE_UNIFORM, GLASS_FOLD_DEPTH_UNIFORM,
GLASS_INNER_SHADOW_UNIFORM, GLASS_KEY_FILL_UNIFORM, GLASS_LAYER_CLIPPED_UNIFORM,
GLASS_LIGHT_DIRECTION_UNIFORM, GLASS_MENISCUS_ABSORPTION_UNIFORM,
GLASS_OPTICAL_PROJECTION_UNIFORM, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
GLASS_OPTICAL_ZOOM_UNIFORM, GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM, GLASS_REFRACTION_CURVE_UNIFORM,
GLASS_REFRACTION_MODE_UNIFORM, GLASS_RESTING_TINT_UNIFORM, GLASS_TOUCH_RADIUS_UNIFORM,
GLASS_TRANSMISSION_REFRACTION_UNIFORM, GraphicsLayer, LIQUID_GLASS_WGSL, LayerShape, Rect,
RenderEffect, RoundedCornerShape, RuntimeShader, TileMode, liquid_glass_runtime_effect,
specialize_liquid_glass,
};
use crate::{appearance::GlassTintAmount, theme::LiquidColors};
thread_local! {
static GLASS_LIGHT_RETURN: Cell<(f32, f32)> = const { Cell::new((0.0, 1.0)) };
}
pub fn set_glass_light_direction(direction: (f32, f32)) {
GLASS_LIGHT_RETURN.with(|cell| cell.set(direction));
}
pub fn glass_light_direction() -> (f32, f32) {
GLASS_LIGHT_RETURN.with(|cell| cell.get())
}
const CAPSULE_CLIP_RADIUS: f32 = 1.0e6;
const CAPSULE_SHADER_RADIUS: f32 = -1.0;
const WCKSRD_OPTICAL_BLUR_RADIUS_PX: f32 = 2.0;
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum LiquidShape {
#[default]
Capsule,
RoundedRect(f32),
Circle,
}
impl LiquidShape {
pub fn clip_shape(&self) -> RoundedCornerShape {
match self {
LiquidShape::Capsule | LiquidShape::Circle => {
RoundedCornerShape::uniform(CAPSULE_CLIP_RADIUS)
}
LiquidShape::RoundedRect(radius) => RoundedCornerShape::uniform(*radius),
}
}
pub fn layer_shape(&self) -> LayerShape {
LayerShape::Rounded(self.clip_shape())
}
fn shader_radius_px(&self, density: f32) -> f32 {
match self {
LiquidShape::Capsule | LiquidShape::Circle => CAPSULE_SHADER_RADIUS,
LiquidShape::RoundedRect(radius) => radius * density,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum GlassVariant {
#[default]
Regular,
Clear,
Lens,
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum GlassRefraction {
#[default]
Radial,
Surface {
reach_dp: f32,
},
EdgeLens {
reach_dp: f32,
},
}
impl GlassRefraction {
fn shader_parameters(self) -> (f32, f32) {
match self {
Self::Radial => (0.0, 0.0),
Self::Surface { reach_dp } => (1.0, normalized_refraction_reach(reach_dp)),
Self::EdgeLens { reach_dp } => (2.0, normalized_refraction_reach(reach_dp)),
}
}
}
fn normalized_refraction_reach(reach_dp: f32) -> f32 {
if reach_dp.is_finite() {
reach_dp.max(0.0)
} else {
0.0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlassShadow {
pub color: Color,
pub radius: f32,
pub offset_y: f32,
pub spread: f32,
}
impl GlassShadow {
pub fn new(color: Color, radius: f32, offset_y: f32, spread: f32) -> Self {
Self {
color,
radius: radius.max(0.0),
offset_y,
spread,
}
}
fn uniforms(self) -> Option<[f32; 8]> {
let values = [
self.color.r(),
self.color.g(),
self.color.b(),
self.color.a(),
self.radius,
self.offset_y,
self.spread,
1.0,
];
(values.iter().all(|value| value.is_finite())
&& self.radius >= 0.0
&& (0.0..=1.0).contains(&self.color.a()))
.then_some(values)
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct GlassDynamics {
pub edge_return_depth_dp: Option<f32>,
pub optical_projection: Option<(f32, f32)>,
pub activity: Option<f32>,
pub resting_tint: Option<Color>,
pub highlight_boost: f32,
pub saturation_boost: f32,
pub tint_alpha_multiplier: Option<f32>,
pub morph: Option<GlassMorph>,
pub touch: Option<(f32, f32, f32)>,
pub touch_radius_dp: Option<f32>,
pub press_depth: Option<f32>,
}
impl GlassDynamics {
fn projection(&self) -> (f32, f32) {
self.optical_projection
.filter(|(x, y)| x.is_finite() && y.is_finite() && *x > 0.0 && *y > 0.0)
.unwrap_or((1.0, 1.0))
}
pub fn touched_up(
mut self,
press: f32,
finger: Option<(f32, f32)>,
center: (f32, f32),
) -> Self {
let press = press.clamp(0.0, 1.0);
if press <= 0.0 {
return self;
}
self.highlight_boost += 0.85 * press;
self.saturation_boost += 0.45 * press;
let (fx, fy) = finger.unwrap_or(center);
let carried = self.touch.map(|(_, _, i)| i).unwrap_or(0.0);
self.touch = Some((fx, fy, (carried + press).clamp(0.0, 1.0)));
self
}
}
fn foreground_is_dark(foreground: Color) -> bool {
let foreground_luma =
0.2126 * foreground.r() + 0.7152 * foreground.g() + 0.0722 * foreground.b();
foreground_luma < 0.5
}
fn boost_tint_saturation(tint: Color, boost: f32) -> Color {
if boost.abs() <= f32::EPSILON {
return tint;
}
let luma = 0.2126 * tint.r() + 0.7152 * tint.g() + 0.0722 * tint.b();
let saturation = (1.0 + boost).max(0.0);
Color::rgba(
(luma + (tint.r() - luma) * saturation).clamp(0.0, 1.0),
(luma + (tint.g() - luma) * saturation).clamp(0.0, 1.0),
(luma + (tint.b() - luma) * saturation).clamp(0.0, 1.0),
tint.a(),
)
}
pub(crate) fn neutral_surface_tint(foreground: Color, light_alpha: f32, dark_alpha: f32) -> Color {
if foreground_is_dark(foreground) {
Color::BLACK.with_alpha(light_alpha.clamp(0.0, 1.0))
} else {
Color::WHITE.with_alpha(dark_alpha.clamp(0.0, 1.0))
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct GlassMorph {
pub node_size: (f32, f32),
pub primary: (f32, f32, f32, f32, f32),
pub shapes: Vec<(f32, f32, f32, f32, f32)>,
pub glue: f32,
pub wobble_amplitude: f32,
pub wobble_phase: f32,
pub bulge_amplitude: f32,
pub bulge_direction: f32,
pub ellipse_blend: f32,
pub capsule_smoothing_dp: f32,
pub deformation: Option<GlassDeformation>,
pub zoom_anchor: (f32, f32),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlassDeformation {
axis: (f32, f32),
along: f32,
}
impl GlassDeformation {
pub fn incompressible(axis: (f32, f32), along: f32) -> Self {
let length = (axis.0 * axis.0 + axis.1 * axis.1).sqrt();
let axis = if length > f32::EPSILON {
(axis.0 / length, axis.1 / length)
} else {
(1.0, 0.0)
};
Self {
axis,
along: along.max(f32::EPSILON),
}
}
pub fn axis(self) -> (f32, f32) {
self.axis
}
pub fn along(self) -> f32 {
self.along
}
pub fn across(self) -> f32 {
1.0 / self.along
}
}
impl GlassMorph {
pub const MAX_SHAPES: usize = 8;
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlassKeyFill {
pub height_dp: f32,
pub curvature: f32,
pub angle_radians: f32,
pub saturation: f32,
pub luma_gain: f32,
pub offset: f32,
pub scale_with_surface: bool,
}
impl GlassKeyFill {
fn uniforms(self) -> Option<[f32; 7]> {
let values = [
self.height_dp,
self.curvature,
self.angle_radians,
self.saturation,
self.luma_gain,
self.offset,
f32::from(self.scale_with_surface),
];
(values.iter().all(|value| value.is_finite())
&& self.height_dp > 0.0
&& self.curvature > 0.0)
.then_some(values)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlassSpectrum {
pub angle_radians: f32,
pub step_dp: f32,
pub vertical_scale: f32,
pub opacity_near: f32,
pub opacity_far: f32,
pub fade_depth_dp: f32,
pub extent_dp: f32,
}
impl GlassSpectrum {
fn uniforms(self) -> Option<[f32; 8]> {
let values = [
self.angle_radians,
self.step_dp,
self.vertical_scale,
self.opacity_near,
self.opacity_far,
self.fade_depth_dp,
self.extent_dp,
1.0,
];
(values.iter().all(|value| value.is_finite())
&& self.vertical_scale >= 0.0
&& (0.0..=1.0).contains(&self.opacity_near)
&& (0.0..=1.0).contains(&self.opacity_far)
&& self.fade_depth_dp >= 0.0
&& self.extent_dp >= 0.0)
.then_some(values)
}
fn capture_reach(self) -> f32 {
self.step_dp.abs() * 3.0 * self.vertical_scale.max(1.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlassFaceResponse {
pub gain: f32,
pub start_dp: f32,
pub end_dp: f32,
pub illumination: f32,
}
impl GlassFaceResponse {
fn uniforms(self) -> Option<[f32; 4]> {
let values = [
1.0 - self.gain,
self.start_dp,
self.end_dp,
self.illumination,
];
(values.iter().all(|value| value.is_finite())
&& (0.0..=1.0).contains(&self.gain)
&& self.start_dp >= 0.0
&& self.end_dp >= self.start_dp)
.then_some(values)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Glass {
pub variant: GlassVariant,
pub shape: LiquidShape,
pub tint: Option<Color>,
pub tint_amount: Option<GlassTintAmount>,
pub blur_radius: Option<f32>,
pub backdrop_blur: Option<(f32, f32)>,
pub saturation: Option<f32>,
pub refraction_depth: f32,
pub refraction_depth_dp: Option<f32>,
pub refraction: GlassRefraction,
pub refraction_curve: f32,
pub dispersion: f32,
pub edge_spectrum: Option<GlassSpectrum>,
pub transmission_refraction: f32,
pub meniscus_absorption: f32,
pub fold_depth: f32,
pub optical_zoom: f32,
pub rim_reflection: f32,
pub ink_recolor: Option<(Color, f32)>,
pub highlight: f32,
pub face_lighting: bool,
pub key_fill: Option<GlassKeyFill>,
pub face_response: Option<GlassFaceResponse>,
pub inner_shadow: Option<GlassShadow>,
pub lift: Option<f32>,
pub contrast: Option<f32>,
pub shadow: bool,
pub shadow_style: Option<GlassShadow>,
pub clip: bool,
pub foreground: Option<Color>,
pub adaptive_frost: f32,
pub adaptive_tone: bool,
}
impl Glass {
pub fn regular() -> Self {
Self {
variant: GlassVariant::Regular,
shape: LiquidShape::Capsule,
tint: None,
tint_amount: None,
blur_radius: None,
backdrop_blur: None,
saturation: None,
refraction_depth: 0.0,
refraction_depth_dp: None,
refraction: GlassRefraction::Radial,
refraction_curve: 0.25,
dispersion: 0.0,
edge_spectrum: None,
transmission_refraction: 0.0,
meniscus_absorption: 1.0,
fold_depth: 0.0,
optical_zoom: 1.0,
rim_reflection: 1.0,
ink_recolor: None,
highlight: 0.9,
face_lighting: true,
key_fill: None,
face_response: None,
inner_shadow: None,
lift: None,
contrast: None,
shadow: true,
shadow_style: None,
clip: true,
foreground: None,
adaptive_frost: 0.65,
adaptive_tone: false,
}
}
pub fn clear() -> Self {
Self {
variant: GlassVariant::Clear,
..Self::regular()
}
}
pub fn lens() -> Self {
Self {
variant: GlassVariant::Lens,
shape: LiquidShape::Capsule,
tint: Some(Color::rgba(1.0, 1.0, 1.0, 0.07)),
tint_amount: None,
blur_radius: None,
backdrop_blur: None,
saturation: None,
refraction_depth: 0.34,
refraction_depth_dp: None,
refraction: GlassRefraction::Radial,
refraction_curve: 1.0,
dispersion: 0.30,
edge_spectrum: None,
transmission_refraction: 1.0,
meniscus_absorption: 1.0,
fold_depth: 0.0,
optical_zoom: 1.0,
rim_reflection: 1.0,
ink_recolor: None,
highlight: 1.15,
face_lighting: true,
key_fill: None,
face_response: None,
inner_shadow: None,
lift: None,
contrast: None,
shadow: true,
shadow_style: None,
clip: true,
foreground: None,
adaptive_frost: 0.0,
adaptive_tone: false,
}
}
pub fn face_lighting(mut self, enabled: bool) -> Self {
self.face_lighting = enabled;
self
}
pub fn key_fill(mut self, lighting: GlassKeyFill) -> Self {
self.key_fill = Some(lighting);
self
}
pub fn face_response(mut self, response: GlassFaceResponse) -> Self {
self.face_response = Some(response);
self
}
pub fn inner_shadow(mut self, shadow: GlassShadow) -> Self {
self.inner_shadow = shadow.uniforms().map(|_| shadow);
self
}
pub fn shape(mut self, shape: LiquidShape) -> Self {
self.shape = shape;
self
}
pub fn tint(mut self, tint: Color) -> Self {
self.tint = Some(tint);
self
}
pub fn blur_radius(mut self, radius_dp: f32) -> Self {
self.blur_radius = Some(radius_dp);
self
}
pub fn saturation(mut self, saturation: f32) -> Self {
self.saturation = Some(saturation);
self
}
pub fn refraction_depth(mut self, fraction: f32) -> Self {
self.refraction_depth = fraction.clamp(0.0, 2.0);
self.refraction_depth_dp = None;
self
}
pub fn refraction_depth_dp(mut self, depth_dp: f32) -> Self {
self.refraction_depth_dp = Some(depth_dp.max(0.0));
self
}
pub fn surface_refraction(mut self, reach_dp: f32) -> Self {
self.refraction = GlassRefraction::Surface {
reach_dp: normalized_refraction_reach(reach_dp),
};
self
}
pub fn edge_spectrum(mut self, spectrum: GlassSpectrum) -> Self {
self.edge_spectrum = spectrum.uniforms().map(|_| spectrum);
self
}
pub fn edge_refraction(mut self, reach_dp: f32) -> Self {
self.refraction = GlassRefraction::EdgeLens {
reach_dp: normalized_refraction_reach(reach_dp),
};
self
}
pub fn refraction_curve(mut self, curve: f32) -> Self {
self.refraction_curve = curve.clamp(0.05, 1.0);
self
}
pub fn dispersion(mut self, strength: f32) -> Self {
self.dispersion = strength.clamp(0.0, 1.0);
self
}
pub fn transmission_refraction(mut self, strength: f32) -> Self {
self.transmission_refraction = strength.clamp(0.0, 1.0);
self
}
pub fn meniscus_absorption(mut self, strength: f32) -> Self {
self.meniscus_absorption = strength.clamp(0.0, 1.0);
self
}
pub fn fold_depth(mut self, depth_dp: f32) -> Self {
self.fold_depth = depth_dp.max(0.0);
self
}
pub fn optical_zoom(mut self, zoom: f32) -> Self {
self.optical_zoom = zoom.max(1.0);
self
}
pub fn rim_reflection(mut self, reflectivity: f32) -> Self {
self.rim_reflection = reflectivity.clamp(0.0, 2.0);
self
}
pub fn ink_recolor(mut self, color: Color, strength: f32) -> Self {
self.ink_recolor = Some((color, strength.clamp(0.0, 1.0)));
self
}
pub fn highlight(mut self, highlight: f32) -> Self {
self.highlight = highlight;
self
}
pub fn lift(mut self, lift: f32) -> Self {
self.lift = Some(lift);
self
}
pub fn contrast(mut self, contrast: f32) -> Self {
self.contrast = Some(contrast.max(0.05));
self
}
pub fn adaptive_frost(mut self, foreground: Color, strength: f32) -> Self {
self.foreground = Some(foreground);
self.adaptive_frost = strength.clamp(0.0, 1.0);
self
}
pub fn adaptive_tone(mut self, foreground: Color) -> Self {
self.foreground = Some(foreground);
self.adaptive_tone = true;
self.adaptive_frost = 0.0;
self
}
pub fn shadow(mut self, shadow: bool) -> Self {
self.shadow = shadow;
self
}
pub fn shadow_style(mut self, shadow: GlassShadow) -> Self {
self.shadow_style = Some(shadow);
self
}
pub fn no_clip(mut self) -> Self {
self.clip = false;
self
}
pub fn tint_amount(mut self, amount: GlassTintAmount) -> Self {
self.tint_amount = Some(amount);
self
}
pub fn backdrop_blur(mut self, radius_dp: f32, opacity: f32) -> Self {
self.backdrop_blur = (radius_dp.is_finite() && opacity.is_finite())
.then_some((radius_dp.max(0.0), opacity.clamp(0.0, 1.0)));
self
}
fn default_blur_radius(&self) -> f32 {
match self.variant {
GlassVariant::Regular => 8.0,
GlassVariant::Clear => 3.0,
GlassVariant::Lens => 0.0,
}
}
fn default_saturation(&self) -> f32 {
match self.variant {
GlassVariant::Regular => 1.5,
GlassVariant::Clear => 1.25,
GlassVariant::Lens => 1.0,
}
}
pub fn backdrop_effect(
&self,
colors: &LiquidColors,
density: f32,
dynamics: GlassDynamics,
) -> RenderEffect {
self.resolve(colors).backdrop_effect(density, dynamics)
}
fn preferred_foreground(&self, colors: &LiquidColors) -> Color {
self.foreground
.filter(|color| {
[color.r(), color.g(), color.b(), color.a()]
.iter()
.all(|value| value.is_finite())
})
.unwrap_or(colors.label)
}
pub(crate) fn resolve(&self, colors: &LiquidColors) -> ResolvedGlass {
let lift = self.lift.unwrap_or(match (self.variant, colors.is_dark) {
(GlassVariant::Regular, false) => 0.42,
(GlassVariant::Regular, true) => -0.38,
(GlassVariant::Clear, false) => 0.12,
(GlassVariant::Clear, true) => -0.12,
(GlassVariant::Lens, false) => 0.10,
(GlassVariant::Lens, true) => -0.08,
});
let foreground = self.preferred_foreground(colors);
let shadow = self.shadow_style.unwrap_or_else(|| {
GlassShadow::new(
Color::BLACK.with_alpha(match (self.variant, colors.is_dark) {
(GlassVariant::Lens, false) => 0.14,
(GlassVariant::Lens, true) => 0.28,
(_, false) => 0.16,
(_, true) => 0.5,
}),
if self.variant == GlassVariant::Lens {
10.0
} else {
22.0
},
if self.variant == GlassVariant::Lens {
3.0
} else {
8.0
},
if self.variant == GlassVariant::Lens {
-6.0
} else {
-2.0
},
)
});
ResolvedGlass {
shape: self.shape,
tint: self.tint.unwrap_or(colors.glass_tint),
tint_amount: self.tint_amount,
backdrop_blur: self.backdrop_blur,
blur_radius_dp: self
.blur_radius
.unwrap_or_else(|| self.default_blur_radius()),
saturation: self.saturation.unwrap_or_else(|| self.default_saturation()),
refraction_depth: self.refraction_depth,
refraction_depth_dp: self.refraction_depth_dp,
refraction: self.refraction,
refraction_curve: self.refraction_curve,
dispersion: self.dispersion,
edge_spectrum: self.edge_spectrum,
transmission_refraction: self.transmission_refraction,
meniscus_absorption: self.meniscus_absorption,
fold_depth: self.fold_depth,
optical_zoom: self.optical_zoom,
rim_reflection: self.rim_reflection,
ink_recolor: self.ink_recolor,
highlight: self.highlight,
face_lighting: self.face_lighting,
key_fill: self.key_fill,
face_response: self.face_response,
inner_shadow: self.inner_shadow,
lift,
contrast: self.contrast.unwrap_or(match self.variant {
GlassVariant::Lens => 1.0,
_ => 1.03,
}),
shadow: self.shadow,
clip: self.clip,
foreground_luma: 0.2126 * foreground.r()
+ 0.7152 * foreground.g()
+ 0.0722 * foreground.b(),
adaptive_frost: self.adaptive_frost,
adaptive_tone: self.adaptive_tone,
rim_style: if self.variant == GlassVariant::Lens {
1.0
} else {
0.0
},
shadow_color: shadow.color,
shadow_radius: shadow.radius,
shadow_offset_y: shadow.offset_y,
shadow_spread: shadow.spread,
}
}
}
impl Default for Glass {
fn default() -> Self {
Self::regular()
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ResolvedGlass {
pub shape: LiquidShape,
pub tint: Color,
tint_amount: Option<GlassTintAmount>,
backdrop_blur: Option<(f32, f32)>,
pub blur_radius_dp: f32,
pub saturation: f32,
pub refraction_depth: f32,
pub refraction_depth_dp: Option<f32>,
pub refraction: GlassRefraction,
pub refraction_curve: f32,
pub dispersion: f32,
pub edge_spectrum: Option<GlassSpectrum>,
pub transmission_refraction: f32,
pub meniscus_absorption: f32,
pub fold_depth: f32,
pub optical_zoom: f32,
pub rim_reflection: f32,
pub ink_recolor: Option<(Color, f32)>,
pub highlight: f32,
pub face_lighting: bool,
pub key_fill: Option<GlassKeyFill>,
pub face_response: Option<GlassFaceResponse>,
pub lift: f32,
pub inner_shadow: Option<GlassShadow>,
pub contrast: f32,
pub shadow: bool,
pub clip: bool,
pub rim_style: f32,
pub foreground_luma: f32,
pub adaptive_frost: f32,
pub adaptive_tone: bool,
pub shadow_color: Color,
pub shadow_radius: f32,
pub shadow_offset_y: f32,
pub shadow_spread: f32,
}
fn set_edge_return_depth(shader: &mut RuntimeShader, depth: Option<f32>) {
if let Some(depth) = depth.filter(|depth| depth.is_finite() && *depth >= 0.0) {
shader.set_float2(GLASS_EDGE_RETURN_DEPTH_UNIFORM, depth, 1.0);
}
}
impl ResolvedGlass {
pub(crate) fn without_transparency(mut self, colors: &LiquidColors) -> Self {
self.tint = colors.surface;
self.tint_amount = None;
self.backdrop_blur = None;
self.blur_radius_dp = 0.0;
self.saturation = 1.0;
self.refraction_depth = 0.0;
self.refraction_depth_dp = None;
self.dispersion = 0.0;
self.edge_spectrum = None;
self.transmission_refraction = 0.0;
self.meniscus_absorption = 0.0;
self.rim_reflection = 0.0;
self.adaptive_frost = 0.0;
self
}
fn clips_in_shader(&self) -> bool {
self.key_fill.and_then(GlassKeyFill::uniforms).is_some()
}
pub(crate) fn backdrop_effect(&self, density: f32, dynamics: GlassDynamics) -> RenderEffect {
self.runtime_effect(density, &dynamics, false)
}
fn blur_radii(&self, density: f32, activity: f32, content_mask: bool) -> (f32, f32) {
if content_mask || self.tint_amount.is_some() {
return (0.0, 0.0);
}
let radius = self.blur_radius_dp * density * activity;
if radius > WCKSRD_OPTICAL_BLUR_RADIUS_PX {
(0.0, radius)
} else {
(radius, 0.0)
}
}
fn runtime_effect(
&self,
density: f32,
dynamics: &GlassDynamics,
content_mask: bool,
) -> RenderEffect {
self.runtime_effect_with_content(density, dynamics, content_mask, false)
}
fn runtime_effect_with_content(
&self,
density: f32,
dynamics: &GlassDynamics,
content_mask: bool,
separate_content: bool,
) -> RenderEffect {
let density = density.max(f32::EPSILON);
let activity = dynamics
.activity
.filter(|value| value.is_finite())
.unwrap_or(1.0)
.clamp(0.0, 1.0);
static SHADER: OnceLock<RuntimeShader> = OnceLock::new();
let mut shader = SHADER
.get_or_init(|| {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_float2(GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM, 0.0, 0.0);
specialize_liquid_glass(&mut shader);
shader
})
.clone();
if let Some(morph) = dynamics.morph.as_ref() {
set_morph_uniforms(&mut shader, morph, activity);
} else {
shader.set_float2(0, 0.0, 0.0);
shader.set_float(6, self.shape.shader_radius_px(density));
}
let projection = dynamics.projection();
shader.set_float2(GLASS_OPTICAL_PROJECTION_UNIFORM, projection.0, projection.1);
let press_depth = dynamics.press_depth.unwrap_or(1.0).clamp(0.0, 1.0);
shader.set_float(9, self.refraction_depth * activity * press_depth);
let (refraction_mode, refraction_reach) = self.refraction.shader_parameters();
shader.set_float(GLASS_REFRACTION_MODE_UNIFORM, refraction_mode);
set_edge_return_depth(&mut shader, dynamics.edge_return_depth_dp);
shader.set_float(
GLASS_EDGE_REFRACTION_REACH_UNIFORM,
refraction_reach * activity * press_depth,
);
shader.set_float(
GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM,
self.refraction_depth_dp.unwrap_or(0.0) * activity * press_depth,
);
shader.set_float(
GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
if self.refraction_depth_dp.is_some() {
1.0
} else {
0.0
},
);
shader.set_float(
GLASS_REFRACTION_CURVE_UNIFORM,
self.refraction_curve * activity,
);
shader.set_float(
GLASS_DISPERSION_UNIFORM,
self.dispersion * activity * press_depth,
);
let spectrum_reach = self.set_spectrum_uniforms(&mut shader);
shader.set_float(
GLASS_TRANSMISSION_REFRACTION_UNIFORM,
self.transmission_refraction * activity,
);
shader.set_float(
GLASS_MENISCUS_ABSORPTION_UNIFORM,
self.meniscus_absorption * press_depth,
);
shader.set_float(
GLASS_FOLD_DEPTH_UNIFORM,
self.fold_depth.max(0.0) * press_depth,
);
shader.set_float(
GLASS_OPTICAL_ZOOM_UNIFORM,
1.0 + (self.optical_zoom - 1.0).max(0.0) * activity,
);
let zoom_anchor = dynamics
.morph
.as_ref()
.map(|morph| morph.zoom_anchor)
.unwrap_or((0.0, 0.0));
shader.set_float2(
GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
zoom_anchor.0,
zoom_anchor.1,
);
shader.set_float(121, self.rim_reflection.max(0.001));
let (ink_color, ink_strength) = self
.ink_recolor
.map(|(color, strength)| (color, strength * activity))
.unwrap_or((Color::TRANSPARENT, 0.0));
shader.set_float(124, ink_color.r());
shader.set_float(125, ink_color.g());
shader.set_float(126, ink_color.b());
shader.set_float(127, ink_strength);
let (light_x, light_y) = glass_light_direction();
shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM, light_x);
shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM + 1, light_y);
self.set_lighting_uniforms(&mut shader, dynamics);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, density);
shader.set_float(
11,
(self.highlight + dynamics.highlight_boost).clamp(0.0, 2.0) * activity,
);
let dynamic_tint = boost_tint_saturation(self.tint, dynamics.saturation_boost);
let dynamic_tint_alpha = (dynamic_tint.a()
* dynamics
.tint_alpha_multiplier
.unwrap_or(1.0)
.clamp(0.0, 2.0)
* activity)
.clamp(0.0, 1.0);
shader.set_float4(
14,
dynamic_tint.r(),
dynamic_tint.g(),
dynamic_tint.b(),
dynamic_tint_alpha,
);
let saturation = (self.saturation + dynamics.saturation_boost).max(0.0);
shader.set_float(18, 1.0 + (saturation - 1.0) * activity);
shader.set_float(20, self.lift * activity);
shader.set_float(21, 0.5 * activity);
shader.set_float2(22, 0.0, 1.0);
shader.set_float(24, 1.0 + (self.contrast - 1.0) * activity);
shader.set_float(28, self.rim_style * activity);
let (wcksrd_blur_radius, gaussian_blur_radius) =
self.blur_radii(density, activity, content_mask);
shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, wcksrd_blur_radius);
shader.set_float(GLASS_ACTIVITY_UNIFORM, activity);
let resting_tint = dynamics.resting_tint.unwrap_or(Color::TRANSPARENT);
shader.set_float4(
GLASS_RESTING_TINT_UNIFORM,
resting_tint.r(),
resting_tint.g(),
resting_tint.b(),
resting_tint.a(),
);
shader.set_float(112, if content_mask { 1.0 } else { 0.0 });
shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, self.adaptive_frost * activity);
shader.set_float(97, self.foreground_luma);
shader.set_float(GLASS_ADAPTIVE_TONE_UNIFORM, f32::from(self.adaptive_tone));
self.set_backdrop_uniforms(&mut shader, density, content_mask);
shader.set_float(
cranpose_ui_graphics::GLASS_FOREGROUND_CONTENT_UNIFORM,
f32::from(separate_content),
);
let dynamic_shadow = !self.clip && self.shadow;
shader.set_float(
102,
if dynamic_shadow {
self.shadow_color.a() * 0.55 * activity
} else {
0.0
},
);
shader.set_float(103, self.shadow_radius);
shader.set_float(104, self.shadow_offset_y);
shader.set_float(105, self.shadow_spread);
let morph_pad = dynamics
.morph
.as_ref()
.map(|morph| {
let (px, py, pw, ph, _) = morph.primary;
let (left, top) = (px - pw * 0.5, py - ph * 0.5);
let (right, bottom) = (px + pw * 0.5, py + ph * 0.5);
let mut shape_reach = 0.0f32;
for (sx, sy, sw, sh, _) in &morph.shapes {
let reach_x = ((sx + sw * 0.5) - right)
.max(left - (sx - sw * 0.5))
.max(0.0);
let reach_y = ((sy + sh * 0.5) - bottom)
.max(top - (sy - sh * 0.5))
.max(0.0);
shape_reach = shape_reach.max(reach_x.max(reach_y));
}
let glue_pad = if morph.shapes.is_empty() {
0.0
} else {
morph.glue * 2.0
};
morph.wobble_amplitude * 2.0 + morph.bulge_amplitude + shape_reach + glue_pad
})
.unwrap_or(0.0);
shader.set_input_padding(
(self.input_padding()
+ if refraction_mode >= 1.5 {
refraction_reach * (26.5 / 9.0) + spectrum_reach
} else {
refraction_reach
}
+ morph_pad
+ wcksrd_blur_radius / density)
* projection.0.max(projection.1).max(1.0),
);
if let Some(morph) = dynamics.morph.as_ref() {
let shadow_reach = if dynamic_shadow {
self.shadow_radius + self.shadow_offset_y.abs() + self.shadow_spread.max(0.0)
} else {
0.0
};
let support = morph_output_support(
morph,
activity,
morph_pad,
shadow_reach,
if dynamic_shadow {
self.shadow_offset_y.abs()
} else {
0.0
},
);
let (cx, cy, _, _, _) = morph.primary;
let support = Rect {
x: cx + (support.x - cx) * projection.0,
y: cy + (support.y - cy) * projection.1,
width: support.width * projection.0,
height: support.height * projection.1,
};
let (node_w, node_h) = morph.node_size;
let overhang = (-support.x)
.max(-support.y)
.max(support.x + support.width - node_w)
.max(support.y + support.height - node_h)
.max(0.0);
shader.set_output_padding((morph_pad + shadow_reach + 4.0).max(overhang));
shader.set_output_support(Some(support));
}
let optical_effect = liquid_glass_runtime_effect(shader);
if gaussian_blur_radius > f32::EPSILON {
RenderEffect::blur_with_edge_treatment(gaussian_blur_radius, TileMode::Mirror)
.then(optical_effect)
} else {
optical_effect
}
}
fn set_backdrop_uniforms(&self, shader: &mut RuntimeShader, density: f32, content_mask: bool) {
if let Some(amount) = self.tint_amount {
shader.set_float(
cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM,
8.0 * density,
);
shader.set_float(
cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM + 1,
amount.percent() / 100.0,
);
}
if !content_mask && let Some((radius, opacity)) = self.backdrop_blur {
shader.set_float2(
cranpose_ui_graphics::GLASS_BACKDROP_BLUR_UNIFORM,
radius * density,
opacity,
);
}
}
fn set_spectrum_uniforms(&self, shader: &mut RuntimeShader) -> f32 {
let spectrum = self
.edge_spectrum
.and_then(|spectrum| spectrum.uniforms().map(|values| (spectrum, values)));
if let Some((spectrum, values)) = spectrum {
for (index, value) in values.into_iter().enumerate() {
shader.set_float(GLASS_EDGE_SPECTRUM_UNIFORM + index, value);
}
spectrum.capture_reach()
} else {
40.787 * self.dispersion
}
}
fn set_lighting_uniforms(&self, shader: &mut RuntimeShader, dynamics: &GlassDynamics) {
let (touch_x, touch_y, touch_intensity) = dynamics.touch.unwrap_or((0.0, 0.0, 0.0));
shader.set_float(118, touch_x);
shader.set_float(119, touch_y);
shader.set_float(120, touch_intensity.clamp(0.0, 1.0));
shader.set_float(
GLASS_FACE_LIGHTING_OFF_UNIFORM,
f32::from(!self.face_lighting),
);
shader.set_float(
GLASS_TOUCH_RADIUS_UNIFORM,
dynamics
.touch_radius_dp
.filter(|radius| radius.is_finite() && *radius > 0.0)
.unwrap_or(58.0),
);
shader.set_float(
GLASS_LAYER_CLIPPED_UNIFORM,
f32::from(self.clip && !self.clips_in_shader()),
);
if let Some(values) = self.key_fill.and_then(GlassKeyFill::uniforms) {
for (index, value) in values.into_iter().enumerate() {
shader.set_float(GLASS_KEY_FILL_UNIFORM + index, value);
}
}
if let Some(values) = self.face_response.and_then(GlassFaceResponse::uniforms) {
for (index, value) in values.into_iter().enumerate() {
shader.set_float(GLASS_FACE_RESPONSE_UNIFORM + index, value);
}
}
if let Some(values) = self.inner_shadow.and_then(GlassShadow::uniforms) {
for (index, value) in values.into_iter().enumerate() {
shader.set_float(GLASS_INNER_SHADOW_UNIFORM + index, value);
}
}
}
fn input_padding(&self) -> f32 {
2.0
}
}
pub trait LiquidModifierExt {
fn glass_effect(self, glass: Glass) -> Modifier;
fn glass_effect_with(
self,
glass: Glass,
dynamics: impl Fn() -> GlassDynamics + 'static,
) -> Modifier;
}
impl LiquidModifierExt for Modifier {
fn glass_effect(self, glass: Glass) -> Modifier {
self.glass_effect_with(glass, GlassDynamics::default)
}
fn glass_effect_with(
self,
glass: Glass,
dynamics: impl Fn() -> GlassDynamics + 'static,
) -> Modifier {
let colors = crate::theme::liquid_colors();
let options = cranpose_services::local_accessibility_options().current();
let resolved = if options.reduce_transparency {
glass.resolve(&colors).without_transparency(&colors)
} else {
glass.resolve(&colors)
};
let shape = resolved.shape;
let mut modifier = self;
if resolved.shadow && resolved.clip {
let shadow_color = resolved.shadow_color;
let (radius, offset_y, spread) = (
resolved.shadow_radius,
resolved.shadow_offset_y,
resolved.shadow_spread,
);
modifier = modifier.drop_shadow(shape.layer_shape(), move |scope| {
scope.radius = radius;
scope.spread = spread;
scope.offset.y = offset_y;
scope.color = shadow_color;
scope.cutout = true;
});
}
let layer = cached_glass_layer(resolved);
modifier.graphics_layer(move || layer(current_density(), dynamics()))
}
}
struct CachedGlassLayer<T> {
density: u32,
light: [u32; 2],
dynamics: GlassDynamics,
value: T,
}
fn cached_glass_layer(resolved: ResolvedGlass) -> impl Fn(f32, GlassDynamics) -> GraphicsLayer {
cached_glass_layers(resolved, false, std::convert::identity)
}
pub(crate) fn cached_glass_content_layers(
resolved: ResolvedGlass,
) -> impl Fn(f32, GlassDynamics) -> [GraphicsLayer; 2] {
cached_glass_layers(resolved, true, |mut layer| {
let Some(RenderEffect::Chain { first, second }) = layer.backdrop_effect.take() else {
panic!("content layers require a sequential edge lens");
};
layer.backdrop_effect = Some((*second).clone());
[
GraphicsLayer {
backdrop_effect: Some((*first).clone()),
..Default::default()
},
layer,
]
})
}
fn cached_glass_layers<T: Clone>(
resolved: ResolvedGlass,
separate_content: bool,
assemble: impl Fn(GraphicsLayer) -> T,
) -> impl Fn(f32, GlassDynamics) -> T {
let cached = RefCell::new(None::<CachedGlassLayer<T>>);
move |density, dynamics| {
let direction = glass_light_direction();
let light = [direction.0, direction.1].map(f32::to_bits);
let mut cached = cached.borrow_mut();
if let Some(previous) = &*cached
&& previous.density == density.to_bits()
&& previous.light == light
&& glass_dynamics_match(&previous.dynamics, &dynamics)
{
return previous.value.clone();
}
let render_effect = ((!resolved.clip && dynamics.morph.is_some())
|| (resolved.clip && resolved.clips_in_shader()))
.then(|| resolved.runtime_effect(density, &dynamics, true));
let layer = GraphicsLayer {
backdrop_effect: Some(resolved.runtime_effect_with_content(
density,
&dynamics,
false,
separate_content,
)),
render_effect,
shape: resolved.shape.layer_shape(),
clip: resolved.clip && !resolved.clips_in_shader(),
..Default::default()
};
let value = assemble(layer);
*cached = Some(CachedGlassLayer {
density: density.to_bits(),
light,
dynamics,
value: value.clone(),
});
value
}
}
fn glass_dynamics_match(a: &GlassDynamics, b: &GlassDynamics) -> bool {
let bits = |d: &GlassDynamics| {
let GlassDynamics {
edge_return_depth_dp,
optical_projection,
activity,
resting_tint,
highlight_boost,
saturation_boost,
tint_alpha_multiplier,
morph: _,
touch,
touch_radius_dp,
press_depth,
} = d;
(
edge_return_depth_dp.map(f32::to_bits),
optical_projection.map(|(x, y)| [x, y].map(f32::to_bits)),
activity.map(f32::to_bits),
resting_tint.map(|Color(r, g, b, a)| [r, g, b, a].map(f32::to_bits)),
[*highlight_boost, *saturation_boost].map(f32::to_bits),
tint_alpha_multiplier.map(f32::to_bits),
touch.map(|(x, y, strength)| [x, y, strength].map(f32::to_bits)),
press_depth.map(f32::to_bits),
touch_radius_dp.map(f32::to_bits),
)
};
bits(a) == bits(b)
&& match (&a.morph, &b.morph) {
(None, None) => true,
(Some(a), Some(b)) => glass_morphs_match(a, b),
_ => false,
}
}
fn glass_morphs_match(a: &GlassMorph, b: &GlassMorph) -> bool {
let shape_bits =
|&(x, y, w, h, r): &(f32, f32, f32, f32, f32)| [x, y, w, h, r].map(f32::to_bits);
let bits = |m: &GlassMorph| {
let GlassMorph {
node_size,
primary,
shapes: _,
glue,
wobble_amplitude,
wobble_phase,
bulge_amplitude,
bulge_direction,
ellipse_blend,
capsule_smoothing_dp,
deformation,
zoom_anchor,
} = m;
(
[
node_size.0,
node_size.1,
*glue,
*wobble_amplitude,
*wobble_phase,
*bulge_amplitude,
*bulge_direction,
*ellipse_blend,
*capsule_smoothing_dp,
zoom_anchor.0,
zoom_anchor.1,
]
.map(f32::to_bits),
shape_bits(primary),
deformation
.map(|GlassDeformation { axis, along }| [axis.0, axis.1, along].map(f32::to_bits)),
)
};
bits(a) == bits(b)
&& a.shapes
.iter()
.map(shape_bits)
.eq(b.shapes.iter().map(shape_bits))
}
fn morph_output_support(
morph: &GlassMorph,
activity: f32,
morph_pad: f32,
shadow_reach: f32,
shadow_offset: f32,
) -> Rect {
let (px, py, pw, ph, _) = morph.primary;
let (along, (ax, ay)) = morph.deformation.map_or((1.0, (1.0, 0.0)), |deformation| {
(
1.0 + (deformation.along() - 1.0) * activity,
deformation.axis(),
)
});
let across = 1.0 / along;
let smallest_strain = along.min(across);
let reach = (morph_pad + shadow_reach + 4.0) / smallest_strain;
let rows = [
(
(along * ax * ax + across * ay * ay).abs(),
((along - across) * ax * ay).abs(),
),
(
((along - across) * ax * ay).abs(),
(along * ay * ay + across * ax * ax).abs(),
),
];
let half = (pw * 0.5 + reach, ph * 0.5 + reach);
let extent_x = rows[0].0 * half.0 + rows[0].1 * half.1;
let extent_y = rows[1].0 * half.0 + rows[1].1 * half.1;
let (mut left, mut top) = (px - extent_x, py - extent_y);
let (mut right, mut bottom) = (px + extent_x, py + extent_y);
for (sx, sy, sw, sh, _) in &morph.shapes {
left = left.min(sx - sw * 0.5 - reach);
top = top.min(sy - sh * 0.5 - reach);
right = right.max(sx + sw * 0.5 + reach);
bottom = bottom.max(sy + sh * 0.5 + reach);
}
Rect {
x: left,
y: top - shadow_offset,
width: right - left,
height: bottom - top + 2.0 * shadow_offset,
}
}
fn set_morph_uniforms(shader: &mut RuntimeShader, morph: &GlassMorph, activity: f32) {
let (node_w, node_h) = morph.node_size;
let (cx, cy, w, h, radius) = morph.primary;
shader.set_float2(0, node_w.max(1.0), node_h.max(1.0));
shader.set_float2(2, cx, cy);
shader.set_float2(4, w, h);
shader.set_float(6, radius);
let count = morph.shapes.len().min(GlassMorph::MAX_SHAPES);
shader.set_float(30, count as f32);
for (index, (sx, sy, sw, sh, sr)) in morph.shapes.iter().take(count).enumerate() {
let base = 36 + index * 5;
shader.set_float(base, *sx);
shader.set_float(base + 1, *sy);
shader.set_float(base + 2, *sw);
shader.set_float(base + 3, *sh);
shader.set_float(base + 4, *sr);
}
shader.set_float(31, morph.glue);
shader.set_float(32, morph.wobble_amplitude * activity);
shader.set_float(33, morph.wobble_phase);
shader.set_float(26, morph.bulge_amplitude * activity);
shader.set_float(27, morph.bulge_direction);
shader.set_float(110, morph.ellipse_blend.clamp(0.0, 1.0) * activity);
shader.set_float(
cranpose_ui_graphics::liquid_glass::GLASS_CAPSULE_SMOOTHING_UNIFORM,
if morph.capsule_smoothing_dp.is_finite() {
morph.capsule_smoothing_dp.max(0.0) * activity
} else {
0.0
},
);
if let Some(deformation) = morph.deformation {
let axis = deformation.axis();
let along = 1.0 + (deformation.along() - 1.0) * activity;
shader.set_float2(106, axis.0, axis.1);
shader.set_float(108, along);
shader.set_float(109, 1.0 / along);
} else {
shader.set_float2(106, 1.0, 0.0);
shader.set_float2(108, 1.0, 1.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backdrop_blur_validates_radius_and_opacity() {
for (radius, opacity, expected) in [
(2.0, 0.5, Some((2.0, 0.5))),
(-2.0, 3.0, Some((0.0, 1.0))),
(2.0, -1.0, Some((2.0, 0.0))),
(f32::NAN, 1.0, None),
(2.0, f32::INFINITY, None),
] {
assert_eq!(
Glass::lens().backdrop_blur(radius, opacity).backdrop_blur,
expected
);
}
let glass = Glass::lens()
.backdrop_blur(2.0, 0.75)
.edge_refraction(0.0)
.resolve(&light_colors());
let shader = terminal_shader(glass.backdrop_effect(3.0, GlassDynamics::default()));
assert_eq!(shader.uniforms()[172..174], [6.0, 0.75]);
let mask = terminal_shader(glass.runtime_effect(3.0, &GlassDynamics::default(), true));
assert_eq!(
mask.uniforms().get(172..174).unwrap_or(&[0.0, 0.0]),
[0.0, 0.0]
);
}
#[test]
fn pane_tint_preserves_the_sharp_source_at_every_percentage() {
for percent in [0.0, 25.0, 50.0, 100.0] {
let amount = GlassTintAmount::from_percent(percent).unwrap();
let glass = Glass::regular()
.adaptive_frost(Color::BLACK, 0.0)
.tint_amount(amount);
assert_eq!(glass.tint_amount, Some(amount));
let effect = glass.backdrop_effect(&light_colors(), 3.0, GlassDynamics::default());
let RenderEffect::Shader { shader } = effect else {
panic!("the sharp backdrop must not be destroyed by a preceding blur");
};
assert_eq!(
shader.uniforms()[cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM],
24.0
);
assert_eq!(
shader.uniforms()[cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM + 1],
percent / 100.0
);
assert_eq!(
shader.substrates(),
&[cranpose_ui_graphics::SubstrateSpec::Blur { radius_px: 24.0 }]
);
}
}
fn light_colors() -> LiquidColors {
LiquidColors::light(Color::from_rgb_u8(0, 122, 255))
}
fn terminal_shader(effect: RenderEffect) -> RuntimeShader {
match effect {
RenderEffect::Shader { shader } => std::sync::Arc::unwrap_or_clone(shader),
RenderEffect::Chain { second, .. } => {
terminal_shader(std::sync::Arc::unwrap_or_clone(second))
}
effect => panic!("expected runtime shader, got {effect:?}"),
}
}
#[test]
fn optical_projection_validates_scales_and_projects_output_support() {
let glass = Glass::lens().resolve(&light_colors());
let mut dynamics = GlassDynamics {
morph: Some(GlassMorph {
node_size: (200.0, 120.0),
primary: (100.0, 60.0, 111.0, 70.0, 35.0),
..Default::default()
}),
..Default::default()
};
let base = terminal_shader(glass.backdrop_effect(3.0, dynamics.clone()));
for projection in [
None,
Some((0.0, 1.0)),
Some((-1.0, 1.0)),
Some((1.0, f32::NAN)),
Some((f32::INFINITY, 1.0)),
] {
dynamics.optical_projection = projection;
let shader = terminal_shader(glass.backdrop_effect(3.0, dynamics.clone()));
assert_eq!(
&shader.uniforms()
[GLASS_OPTICAL_PROJECTION_UNIFORM..GLASS_OPTICAL_PROJECTION_UNIFORM + 2],
&[1.0, 1.0]
);
assert_eq!(shader.output_support(), base.output_support());
}
dynamics.optical_projection = Some((1.2, 0.8));
let projected = terminal_shader(glass.backdrop_effect(3.0, dynamics));
assert_eq!(
&projected.uniforms()
[GLASS_OPTICAL_PROJECTION_UNIFORM..GLASS_OPTICAL_PROJECTION_UNIFORM + 2],
&[1.2, 0.8]
);
let a = base.output_support().unwrap();
let b = projected.output_support().unwrap();
assert!((b.width - a.width * 1.2).abs() < 0.001);
assert!((b.height - a.height * 0.8).abs() < 0.001);
}
#[test]
fn content_layers_preserve_the_complete_material_effect_and_cache() {
let resolved = Glass::lens()
.edge_refraction(9.0)
.no_clip()
.resolve(&light_colors());
let layers = cached_glass_content_layers(resolved.clone());
for density in [1.0, 3.0] {
for activity in [0.0, 0.5, 1.0] {
let dynamics = GlassDynamics {
activity: Some(activity),
..Default::default()
};
let actual = layers(density, dynamics.clone());
let joined = actual[0]
.backdrop_effect
.clone()
.unwrap()
.then(actual[1].backdrop_effect.clone().unwrap());
assert_eq!(
joined,
resolved.runtime_effect_with_content(density, &dynamics, false, true)
);
assert!(actual[0].render_effect.is_none());
assert!(!actual[0].clip);
assert_eq!(
terminal_shader(actual[1].backdrop_effect.clone().unwrap()).uniforms()[147],
3.0
);
let mut repeated = layers(density, dynamics.clone());
assert_eq!(actual, repeated);
repeated[0].backdrop_effect = None;
assert_eq!(actual, layers(density, dynamics));
}
}
}
#[test]
fn cached_glass_layers_refresh_all_live_inputs_and_keep_clones_independent() {
let resolved = Glass::regular().no_clip().resolve(&light_colors());
let layer = cached_glass_layer(resolved.clone());
let mut dynamics = GlassDynamics::default();
let mut density = 2.0;
let mut previous = None;
for step in 0..16 {
match step {
1 => density = 3.0,
2 => set_glass_light_direction((0.5, -0.5)),
3 => dynamics.highlight_boost = 0.25,
4 => dynamics.saturation_boost = 0.4,
5 => dynamics.activity = Some(0.5),
6 => dynamics.resting_tint = Some(Color::RED),
7 => dynamics.tint_alpha_multiplier = Some(0.3),
8 => dynamics.touch = Some((5.0, 8.0, 0.7)),
9 => dynamics.press_depth = Some(0.4),
10 => {
dynamics.morph = Some(GlassMorph {
node_size: (60.0, 30.0),
primary: (30.0, 15.0, 50.0, 20.0, 4.0),
shapes: vec![(10.0, 15.0, 8.0, 8.0, -1.0)],
..Default::default()
})
}
11 => {
let morph = dynamics.morph.as_mut().unwrap();
morph.primary.0 += 3.0;
morph.shapes[0].0 += 2.0;
morph.deformation = Some(GlassDeformation::incompressible((1.0, 1.0), 1.3));
}
12 => dynamics.morph = None,
13 => dynamics.touch_radius_dp = Some(144.0),
14 => dynamics.edge_return_depth_dp = Some(3.5),
15 => dynamics.optical_projection = Some((1.2, 0.8)),
_ => {}
}
let actual = layer(density, dynamics.clone());
let expected = GraphicsLayer {
backdrop_effect: Some(resolved.backdrop_effect(density, dynamics.clone())),
render_effect: dynamics
.morph
.as_ref()
.map(|_| resolved.runtime_effect(density, &dynamics, true)),
shape: resolved.shape.layer_shape(),
clip: resolved.clip,
..Default::default()
};
assert_eq!(actual, expected, "live input step {step}");
assert_ne!(
previous.as_ref(),
Some(&actual),
"input change must reach the layer at step {step}"
);
let mut repeated = layer(density, dynamics.clone());
assert_eq!(repeated, actual);
repeated.backdrop_effect = None;
assert_eq!(layer(density, dynamics.clone()), actual);
previous = Some(actual);
}
set_glass_light_direction((0.0, 1.0));
let replacement = cached_glass_layer(
Glass::clear()
.shape(LiquidShape::RoundedRect(9.0))
.resolve(&light_colors()),
);
assert_ne!(
replacement(density, dynamics.clone()),
layer(density, dynamics)
);
}
#[test]
fn glass_cache_input_equality_preserves_signed_zero_and_nan_bits() {
let mut a = GlassDynamics {
touch: Some((0.0, 1.0, 0.5)),
..Default::default()
};
let mut b = a.clone();
b.touch.as_mut().unwrap().0 = -0.0;
assert!(!glass_dynamics_match(&a, &b));
a.highlight_boost = f32::NAN;
assert!(glass_dynamics_match(&a, &a));
a.morph = Some(GlassMorph::default());
b = a.clone();
b.morph.as_mut().unwrap().zoom_anchor.0 = -0.0;
assert!(!glass_dynamics_match(&a, &b));
b = a.clone();
b.morph
.as_mut()
.unwrap()
.shapes
.push((0.0, 1.0, 2.0, 3.0, 4.0));
assert!(!glass_dynamics_match(&a, &b));
}
#[test]
fn glass_light_direction_defaults_overhead_and_reaches_the_shader() {
assert_eq!(glass_light_direction(), (0.0, 1.0));
let resolved = Glass::regular().resolve(&light_colors());
let effect = resolved.backdrop_effect(2.0, GlassDynamics::default());
let shader = terminal_shader(effect);
let u = shader.uniforms();
assert_eq!(u[GLASS_LIGHT_DIRECTION_UNIFORM], 0.0);
assert_eq!(u[GLASS_LIGHT_DIRECTION_UNIFORM + 1], 1.0);
set_glass_light_direction((1.0, 0.0));
let effect = resolved.backdrop_effect(2.0, GlassDynamics::default());
let u_rotated = terminal_shader(effect);
let u_rotated = u_rotated.uniforms();
assert_eq!(u_rotated[GLASS_LIGHT_DIRECTION_UNIFORM], 1.0);
assert_eq!(u_rotated[GLASS_LIGHT_DIRECTION_UNIFORM + 1], 0.0);
set_glass_light_direction((0.0, 1.0));
}
#[test]
fn liquid_shape_builds_matching_clip_and_layer_shapes() {
for shape in [
LiquidShape::Capsule,
LiquidShape::Circle,
LiquidShape::RoundedRect(12.0),
] {
assert_eq!(shape.layer_shape(), LayerShape::Rounded(shape.clip_shape()));
}
}
#[test]
fn glass_shadow_clamps_negative_radius() {
let shadow = GlassShadow::new(Color::BLACK, -2.0, 3.0, -1.0);
assert_eq!(shadow.radius, 0.0);
assert_eq!(shadow.offset_y, 3.0);
assert_eq!(shadow.spread, -1.0);
}
#[test]
fn incompressible_deformation_normalizes_axis_and_conserves_area() {
let deformation = GlassDeformation::incompressible((3.0, 4.0), 1.25);
assert_eq!(deformation.axis(), (0.6, 0.8));
assert_eq!(deformation.along(), 1.25);
assert!((deformation.along() * deformation.across() - 1.0).abs() < 1.0e-6);
assert_eq!(
GlassDeformation::incompressible((0.0, 0.0), 0.0).axis(),
(1.0, 0.0)
);
}
#[test]
fn glass_builders_clamp_physical_inputs() {
let glass = Glass::lens()
.shape(LiquidShape::Circle)
.tint(Color::BLACK)
.blur_radius(-2.0)
.saturation(1.2)
.refraction_depth(3.0)
.refraction_depth_dp(-3.0)
.refraction_curve(2.0)
.dispersion(2.0)
.transmission_refraction(2.0)
.meniscus_absorption(2.0)
.highlight(0.4)
.lift(-0.2)
.adaptive_frost(Color::WHITE, 2.0)
.shadow(false)
.no_clip();
assert_eq!(glass.shape, LiquidShape::Circle);
assert_eq!(glass.tint, Some(Color::BLACK));
assert_eq!(glass.blur_radius, Some(-2.0));
assert_eq!(glass.saturation, Some(1.2));
assert_eq!(glass.refraction_depth, 2.0);
assert_eq!(glass.refraction_depth_dp, Some(0.0));
assert_eq!(glass.refraction_curve, 1.0);
assert_eq!(glass.dispersion, 1.0);
assert_eq!(glass.transmission_refraction, 1.0);
assert_eq!(glass.meniscus_absorption, 1.0);
assert_eq!(glass.highlight, 0.4);
assert_eq!(glass.lift, Some(-0.2));
assert_eq!(glass.adaptive_frost, 1.0);
assert!(!glass.shadow);
assert!(!glass.clip);
}
#[test]
fn material_variants_resolve_distinct_frost_levels() {
let regular = Glass::regular().resolve(&light_colors());
let clear = Glass::clear().resolve(&light_colors());
let lens = Glass::lens().resolve(&light_colors());
assert!(regular.blur_radius_dp > clear.blur_radius_dp);
assert!(clear.blur_radius_dp > lens.blur_radius_dp);
assert!(regular.saturation > clear.saturation);
assert_eq!(lens.rim_style, 1.0);
assert_eq!(regular.refraction_depth, 0.0);
assert_eq!(regular.transmission_refraction, 0.0);
assert_eq!(regular.refraction_curve, 0.25);
assert_eq!(lens.refraction_curve, 1.0);
assert_eq!(regular.dispersion, 0.0);
assert_eq!(lens.dispersion, 0.30);
}
#[test]
fn neutral_surface_helpers_follow_foreground_polarity() {
assert_eq!(
neutral_surface_tint(Color::BLACK, 0.08, 0.10),
Color::BLACK.with_alpha(0.08)
);
assert_eq!(
neutral_surface_tint(Color::WHITE, 0.08, 0.10),
Color::WHITE.with_alpha(0.10)
);
}
#[test]
fn dynamic_saturation_reaches_the_material_tint() {
let resting = Color::from_rgb_u8(0, 199, 208);
let raised = boost_tint_saturation(resting, 0.55);
assert_eq!(raised.r(), 0.0);
assert!(raised.g() > resting.g());
assert!(raised.b() > resting.b());
assert_eq!(raised.a(), resting.a());
}
#[test]
fn template_specialization_follows_each_materials_dispersion_and_activity() {
for (dispersion, activity) in [(0.6, 0.0), (0.0, 0.5), (0.3, 1.0), (0.0, 0.0)] {
let resolved = Glass::regular()
.dispersion(dispersion)
.resolve(&light_colors());
let mut shader = terminal_shader(resolved.backdrop_effect(
1.0,
GlassDynamics {
activity: Some(activity),
..GlassDynamics::default()
},
));
cranpose_ui_graphics::specialize_liquid_glass_with_folds(&mut shader, true);
assert_eq!(
shader.uniforms()[GLASS_DISPERSION_UNIFORM],
dispersion * activity
);
assert_eq!(shader.uniforms()[GLASS_ACTIVITY_UNIFORM], activity);
assert_eq!(
shader
.overrides()
.iter()
.any(|(name, _)| { *name == cranpose_ui_graphics::GLASS_DISPERSION_OFF_FLAG }),
dispersion * activity == 0.0
);
}
}
fn spectrum_fixture() -> GlassSpectrum {
GlassSpectrum {
angle_radians: 0.7,
step_dp: -1.5,
vertical_scale: 1.25,
opacity_near: 0.25,
opacity_far: 0.75,
fade_depth_dp: 3.5,
extent_dp: 12.0,
}
}
#[test]
fn edge_spectrum_preserves_signed_taps_and_captures_every_ray() {
let spectrum = spectrum_fixture();
let material = Glass::lens()
.edge_refraction(9.0)
.dispersion(0.0)
.edge_spectrum(spectrum);
assert_eq!(material.edge_spectrum, Some(spectrum));
let resolved = material.resolve(&light_colors());
let shader = terminal_shader(resolved.backdrop_effect(3.0, GlassDynamics::default()));
assert_eq!(
&shader.uniforms()[GLASS_EDGE_SPECTRUM_UNIFORM..GLASS_EDGE_SPECTRUM_UNIFORM + 8],
&spectrum.uniforms().unwrap()
);
let ordinary = Glass::lens()
.edge_refraction(9.0)
.dispersion(0.0)
.resolve(&light_colors());
let base = terminal_shader(ordinary.backdrop_effect(3.0, GlassDynamics::default()));
assert!((shader.input_padding() - base.input_padding() - 5.625).abs() < 0.001);
}
#[test]
fn edge_spectrum_rejects_invalid_fields_at_both_entry_points() {
for index in 0..7 {
for invalid in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let mut spectrum = spectrum_fixture();
let fields = [
&mut spectrum.angle_radians,
&mut spectrum.step_dp,
&mut spectrum.vertical_scale,
&mut spectrum.opacity_near,
&mut spectrum.opacity_far,
&mut spectrum.fade_depth_dp,
&mut spectrum.extent_dp,
];
*fields.into_iter().nth(index).unwrap() = invalid;
assert_eq!(Glass::lens().edge_spectrum(spectrum).edge_spectrum, None);
let material = Glass {
edge_spectrum: Some(spectrum),
..Glass::lens()
};
let shader = terminal_shader(
material
.resolve(&light_colors())
.backdrop_effect(1.0, GlassDynamics::default()),
);
assert_eq!(
shader
.uniforms()
.get(GLASS_EDGE_SPECTRUM_UNIFORM + 7)
.copied()
.unwrap_or(0.0),
0.0
);
}
}
for index in 2..7 {
let mut spectrum = spectrum_fixture();
let fields = [
&mut spectrum.vertical_scale,
&mut spectrum.opacity_near,
&mut spectrum.opacity_far,
&mut spectrum.fade_depth_dp,
&mut spectrum.extent_dp,
];
*fields.into_iter().nth(index - 2).unwrap() = -0.1;
assert_eq!(Glass::lens().edge_spectrum(spectrum).edge_spectrum, None);
}
for near in [true, false] {
let mut spectrum = spectrum_fixture();
if near {
spectrum.opacity_near = 1.1;
} else {
spectrum.opacity_far = 1.1;
}
assert_eq!(Glass::lens().edge_spectrum(spectrum).edge_spectrum, None);
}
let zero = GlassSpectrum {
angle_radians: 0.0,
step_dp: 0.0,
vertical_scale: 0.0,
opacity_near: 0.0,
opacity_far: 1.0,
fade_depth_dp: 0.0,
extent_dp: 0.0,
};
assert_eq!(Glass::lens().edge_spectrum(zero).edge_spectrum, Some(zero));
}
#[test]
fn material_template_preserves_each_instances_optical_zoom_anchor() {
let resolved = Glass::lens().resolve(&light_colors());
for anchor in [(3.5, -2.25), (0.0, 0.0), (-7.0, 11.0)] {
let shader = terminal_shader(resolved.backdrop_effect(
1.5,
GlassDynamics {
morph: Some(GlassMorph {
node_size: (120.0, 72.0),
primary: (60.0, 36.0, 96.0, 52.0, -1.0),
zoom_anchor: anchor,
..Default::default()
}),
..Default::default()
},
));
assert_eq!(
&shader.uniforms()
[GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM..GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM + 2],
&[anchor.0, anchor.1]
);
}
}
#[test]
fn key_fill_preserves_valid_parameters_and_rejects_invalid_fields() {
let lighting = GlassKeyFill {
height_dp: 1.0,
curvature: 0.8,
angle_radians: -std::f32::consts::FRAC_PI_4,
saturation: 1.5,
luma_gain: 0.4367,
offset: 1.125,
scale_with_surface: true,
};
for glass in [Glass::regular(), Glass::clear(), Glass::lens()] {
assert!(glass.key_fill.is_none());
let material = glass.key_fill(lighting);
assert_eq!(material.key_fill, Some(lighting));
let shader = terminal_shader(material.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(
&shader.uniforms()[GLASS_KEY_FILL_UNIFORM..GLASS_KEY_FILL_UNIFORM + 7],
&lighting.uniforms().unwrap()
);
}
for scale_with_surface in [false, true] {
let value = GlassKeyFill {
scale_with_surface,
..lighting
};
assert_eq!(value.uniforms().unwrap()[6], f32::from(scale_with_surface));
let layer = cached_glass_layer(Glass::clear().key_fill(value).resolve(&light_colors()))(
3.0,
GlassDynamics::default(),
);
assert!(!layer.clip);
assert!(layer.render_effect.is_some());
assert!(layer.backdrop_effect.is_some());
}
for index in 0..6 {
for invalid in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let mut value = lighting;
match index {
0 => value.height_dp = invalid,
1 => value.curvature = invalid,
2 => value.angle_radians = invalid,
3 => value.saturation = invalid,
4 => value.luma_gain = invalid,
_ => value.offset = invalid,
}
let shader = terminal_shader(Glass::clear().key_fill(value).backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(
shader
.uniforms()
.get(GLASS_KEY_FILL_UNIFORM)
.copied()
.unwrap_or_default(),
0.0
);
}
}
for invalid in [0.0, -1.0] {
assert!(
GlassKeyFill {
height_dp: invalid,
..lighting
}
.uniforms()
.is_none()
);
assert!(
GlassKeyFill {
curvature: invalid,
..lighting
}
.uniforms()
.is_none()
);
}
}
#[test]
fn independent_edge_return_depth_preserves_zero_and_rejects_invalid_values() {
for depth in [
None,
Some(0.0),
Some(3.5),
Some(-1.0),
Some(f32::NAN),
Some(f32::INFINITY),
] {
let shader = terminal_shader(
Glass::lens()
.edge_refraction(9.0)
.refraction_depth_dp(36.0)
.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics {
edge_return_depth_dp: depth,
..GlassDynamics::default()
},
),
);
let valid = depth.filter(|value| value.is_finite() && *value >= 0.0);
assert_eq!(
shader
.uniforms()
.get(GLASS_EDGE_RETURN_DEPTH_UNIFORM)
.copied()
.unwrap_or(0.0),
valid.unwrap_or(0.0)
);
assert_eq!(
shader
.uniforms()
.get(GLASS_EDGE_RETURN_DEPTH_UNIFORM + 1)
.copied()
.unwrap_or(0.0),
f32::from(valid.is_some())
);
assert_eq!(
shader.uniforms()[GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM],
36.0
);
}
}
#[test]
fn inner_shadow_validates_and_resolves_its_gaussian_profile() {
let shadow = GlassShadow::new(Color::BLACK.with_alpha(0.12), 3.0, 7.0, 0.0);
for glass in [Glass::regular(), Glass::clear(), Glass::lens()] {
assert_eq!(glass.inner_shadow, None);
let material = glass.inner_shadow(shadow);
assert_eq!(material.inner_shadow, Some(shadow));
let shader = terminal_shader(material.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(
&shader.uniforms()[GLASS_INNER_SHADOW_UNIFORM..GLASS_INNER_SHADOW_UNIFORM + 8],
&shadow.uniforms().unwrap()
);
}
for alpha in [0.0, 1.0] {
assert!(
Glass::lens()
.inner_shadow(GlassShadow::new(
Color::BLACK.with_alpha(alpha),
0.0,
-2.0,
-3.0
))
.inner_shadow
.is_some()
);
}
for index in 0..7 {
for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let mut values = shadow.uniforms().unwrap();
values[index] = value;
let invalid = GlassShadow {
color: Color(values[0], values[1], values[2], values[3]),
radius: values[4],
offset_y: values[5],
spread: values[6],
};
assert_eq!(Glass::lens().inner_shadow(invalid).inner_shadow, None);
let material = Glass {
inner_shadow: Some(invalid),
..Glass::lens()
};
let shader = terminal_shader(material.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(
shader
.uniforms()
.get(GLASS_INNER_SHADOW_UNIFORM + 7)
.copied()
.unwrap_or(0.0),
0.0
);
}
}
for invalid in [
GlassShadow {
radius: -1.0,
..shadow
},
GlassShadow {
color: Color(0.0, 0.0, 0.0, -0.1),
..shadow
},
GlassShadow {
color: Color(0.0, 0.0, 0.0, 1.1),
..shadow
},
] {
assert_eq!(Glass::lens().inner_shadow(invalid).inner_shadow, None);
}
}
#[test]
fn adaptive_tone_resolves_foreground_and_declares_the_shared_mean() {
use cranpose_ui_graphics::SubstrateSpec;
for foreground in [Color::BLACK, Color::WHITE, Color(f32::NAN, 0.0, 0.0, 1.0)] {
let material = Glass::clear().adaptive_tone(foreground);
assert!(material.adaptive_tone);
let resolved = material.resolve(&light_colors());
assert!(resolved.foreground_luma.is_finite());
let shader = terminal_shader(resolved.backdrop_effect(3.0, GlassDynamics::default()));
assert_eq!(shader.uniforms()[GLASS_ADAPTIVE_TONE_UNIFORM], 1.0);
assert_eq!(shader.substrates(), &[SubstrateSpec::Mean]);
let expected = if foreground.r().is_finite() {
foreground.r()
} else {
0.2126 * light_colors().label.r()
+ 0.7152 * light_colors().label.g()
+ 0.0722 * light_colors().label.b()
};
assert!((resolved.foreground_luma - expected).abs() < 0.0001);
}
assert!(!Glass::clear().adaptive_tone);
assert!(!Glass::regular().adaptive_tone);
}
#[test]
fn face_response_preserves_valid_ranges_and_rejects_invalid_values() {
let response = GlassFaceResponse {
gain: 0.97,
start_dp: 1.0 / 3.0,
end_dp: 2.0 / 3.0,
illumination: 0.05,
};
for glass in [Glass::regular(), Glass::clear(), Glass::lens()] {
assert!(glass.face_response.is_none());
let material = glass.face_response(response);
assert_eq!(material.face_response, Some(response));
let shader = terminal_shader(material.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(
&shader.uniforms()[GLASS_FACE_RESPONSE_UNIFORM..GLASS_FACE_RESPONSE_UNIFORM + 4],
&response.uniforms().unwrap()
);
}
for gain in [0.0, 1.0] {
assert!(
GlassFaceResponse {
gain,
start_dp: 0.0,
end_dp: 0.0,
..response
}
.uniforms()
.is_some()
);
}
let mut invalid = vec![
GlassFaceResponse {
gain: -0.1,
..response
},
GlassFaceResponse {
gain: 1.1,
..response
},
GlassFaceResponse {
start_dp: -1.0,
..response
},
GlassFaceResponse {
end_dp: 0.0,
..response
},
];
for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
invalid.extend([
GlassFaceResponse {
gain: value,
..response
},
GlassFaceResponse {
start_dp: value,
..response
},
GlassFaceResponse {
end_dp: value,
..response
},
GlassFaceResponse {
illumination: value,
..response
},
]);
}
for response in invalid {
assert!(response.uniforms().is_none());
let shader = terminal_shader(Glass::clear().face_response(response).backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert!(
shader
.uniforms()
.get(GLASS_FACE_RESPONSE_UNIFORM..GLASS_FACE_RESPONSE_UNIFORM + 4)
.is_none_or(|values| values.iter().all(|value| *value == 0.0))
);
}
}
#[test]
fn face_lighting_is_explicit_and_preserves_each_material_default() {
for glass in [Glass::regular(), Glass::clear(), Glass::lens()] {
assert!(glass.face_lighting);
for enabled in [false, true] {
let material = glass.clone().face_lighting(enabled);
assert_eq!(material.face_lighting, enabled);
let resolved = material.resolve(&light_colors());
assert_eq!(resolved.face_lighting, enabled);
let shader =
terminal_shader(resolved.backdrop_effect(3.0, GlassDynamics::default()));
assert_eq!(
shader.uniforms()[GLASS_FACE_LIGHTING_OFF_UNIFORM],
f32::from(!enabled)
);
}
}
}
#[test]
fn layer_clipping_has_one_owner_for_material_coverage() {
for clipped in [false, true] {
let mut glass = Glass::regular();
if !clipped {
glass = glass.no_clip();
}
let shader = terminal_shader(glass.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(
shader.uniforms()[GLASS_LAYER_CLIPPED_UNIFORM],
f32::from(clipped)
);
}
}
#[test]
fn touch_radius_preserves_valid_values_and_normalizes_invalid_values() {
for radius in [
None,
Some(-1.0),
Some(0.0),
Some(f32::NAN),
Some(f32::INFINITY),
Some(f32::NEG_INFINITY),
Some(0.5),
Some(144.0),
] {
let expected = radius
.filter(|value| value.is_finite() && *value > 0.0)
.unwrap_or(58.0);
for density in [1.0, 3.0] {
let shader = terminal_shader(Glass::regular().backdrop_effect(
&light_colors(),
density,
GlassDynamics {
touch_radius_dp: radius,
..Default::default()
},
));
assert_eq!(shader.uniforms()[GLASS_TOUCH_RADIUS_UNIFORM], expected);
}
}
}
#[test]
fn material_instances_share_source_without_sharing_uniforms_or_overrides() {
let resolved = Glass::regular().resolve(&light_colors());
let mut first = terminal_shader(resolved.backdrop_effect(2.0, GlassDynamics::default()));
let untouched = terminal_shader(resolved.backdrop_effect(1.0, GlassDynamics::default()));
let unused_slot = RuntimeShader::MAX_USER_UNIFORMS - 1;
first.set_float(unused_slot, 17.0);
first.set_override("INSTANCE_ONLY", 1.0);
let second = terminal_shader(resolved.backdrop_effect(1.0, GlassDynamics::default()));
assert_eq!(second.uniforms(), untouched.uniforms());
assert_eq!(second.overrides(), untouched.overrides());
assert_eq!(
second.uniforms().get(unused_slot).copied().unwrap_or(0.0),
0.0
);
assert!(
!second
.overrides()
.iter()
.any(|(name, _)| *name == "INSTANCE_ONLY")
);
assert_eq!(first.uniforms()[unused_slot], 17.0);
assert_eq!(first.uniforms()[GLASS_EFFECT_DENSITY_UNIFORM], 2.0);
assert_eq!(second.uniforms()[GLASS_EFFECT_DENSITY_UNIFORM], 1.0);
assert_eq!(first.source().as_ptr(), second.source().as_ptr());
}
#[test]
fn resolved_material_packs_wcksrd_and_dynamic_tint() {
let resolved = Glass::lens()
.refraction_depth(0.72)
.refraction_depth_dp(21.0)
.refraction_curve(0.8)
.dispersion(0.42)
.transmission_refraction(0.35)
.meniscus_absorption(0.3)
.blur_radius(3.0)
.tint(Color::BLACK.with_alpha(0.8))
.resolve(&light_colors());
let effect = resolved.backdrop_effect(
2.0,
GlassDynamics {
highlight_boost: 0.2,
saturation_boost: 0.35,
tint_alpha_multiplier: Some(0.25),
..Default::default()
},
);
let RenderEffect::Chain { first, .. } = &effect else {
panic!("macroscopic frost must precede the wcKSRD optical pass");
};
assert!(matches!(
first.as_ref(),
RenderEffect::Blur {
radius_x: 6.0,
radius_y: 6.0,
..
}
));
let shader = terminal_shader(effect);
assert_eq!(shader.uniforms()[9], 0.72);
assert_eq!(
shader.uniforms()[GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM],
21.0
);
assert_eq!(
shader.uniforms()[GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM],
1.0
);
assert_eq!(shader.uniforms()[GLASS_REFRACTION_CURVE_UNIFORM], 0.8);
assert_eq!(shader.uniforms()[GLASS_DISPERSION_UNIFORM], 0.42);
assert_eq!(
shader.uniforms()[GLASS_TRANSMISSION_REFRACTION_UNIFORM],
0.35
);
assert_eq!(shader.uniforms()[GLASS_MENISCUS_ABSORPTION_UNIFORM], 0.3);
assert_eq!(shader.uniforms()[11], resolved.highlight + 0.2);
assert_eq!(shader.uniforms()[18], resolved.saturation + 0.35);
assert!((shader.uniforms()[17] - 0.2).abs() < 1.0e-6);
assert_eq!(shader.uniforms()[GLASS_BLUR_RADIUS_UNIFORM], 0.0);
assert_eq!(shader.uniforms()[GLASS_EFFECT_DENSITY_UNIFORM], 2.0);
}
#[test]
fn heavy_backdrop_blur_rides_the_gaussian_pass_alone() {
let effect = Glass::regular()
.resolve(&light_colors())
.backdrop_effect(3.0, GlassDynamics::default());
let RenderEffect::Chain { first, .. } = &effect else {
panic!("heavy blur must ride the separable Gaussian pre-pass");
};
assert!(matches!(
first.as_ref(),
RenderEffect::Blur {
radius_x: 24.0,
radius_y: 24.0,
..
}
));
assert_eq!(
terminal_shader(effect).uniforms()[GLASS_BLUR_RADIUS_UNIFORM],
0.0
);
}
#[test]
fn featherweight_backdrop_blur_stays_in_the_optical_tap() {
let RenderEffect::Shader { shader } = Glass::regular()
.blur_radius(0.5)
.resolve(&light_colors())
.backdrop_effect(3.0, GlassDynamics::default())
else {
panic!("a sub-cap blur must not build a Gaussian chain");
};
assert_eq!(shader.uniforms()[GLASS_BLUR_RADIUS_UNIFORM], 1.5);
}
#[test]
fn raised_tint_density_stays_premultiplied_alpha_safe() {
let effect = Glass::lens()
.tint(Color::WHITE.with_alpha(0.8))
.resolve(&light_colors())
.backdrop_effect(
1.0,
GlassDynamics {
tint_alpha_multiplier: Some(2.0),
..Default::default()
},
);
assert_eq!(terminal_shader(effect).uniforms()[17], 1.0);
}
#[test]
fn optical_activity_reaches_identity_without_removing_the_glass_geometry() {
let resolved = Glass::lens()
.refraction_depth(0.72)
.refraction_curve(0.8)
.dispersion(0.42)
.blur_radius(3.0)
.saturation(1.4)
.highlight(0.6)
.lift(0.2)
.tint(Color::BLACK.with_alpha(0.8))
.no_clip()
.resolve(&light_colors());
let morph = GlassMorph {
node_size: (120.0, 72.0),
primary: (60.0, 36.0, 96.0, 52.0, -1.0),
wobble_amplitude: 3.0,
bulge_amplitude: 4.0,
deformation: Some(GlassDeformation::incompressible((1.0, 0.0), 1.25)),
..Default::default()
};
let RenderEffect::Shader { shader } = resolved.backdrop_effect(
2.0,
GlassDynamics {
activity: Some(0.0),
morph: Some(morph),
..Default::default()
},
) else {
panic!("material must resolve to the shared wcKSRD shader");
};
let uniforms = shader.uniforms();
assert_eq!(uniforms[GLASS_ACTIVITY_UNIFORM], 0.0);
assert_eq!(uniforms[9], 0.0);
assert_eq!(uniforms[GLASS_REFRACTION_CURVE_UNIFORM], 0.0);
assert_eq!(uniforms[GLASS_DISPERSION_UNIFORM], 0.0);
assert_eq!(uniforms[GLASS_TRANSMISSION_REFRACTION_UNIFORM], 0.0);
assert_eq!(uniforms[11], 0.0);
assert_eq!(uniforms[17], 0.0);
assert_eq!(uniforms[18], 1.0);
assert_eq!(uniforms[20], 0.0);
assert_eq!(uniforms[21], 0.0);
assert_eq!(uniforms[24], 1.0);
assert_eq!(uniforms[28], 0.0);
assert_eq!(uniforms[GLASS_BLUR_RADIUS_UNIFORM], 0.0);
assert_eq!(uniforms[91], 0.0);
assert_eq!(uniforms[102], 0.0);
assert_eq!(
&uniforms[GLASS_RESTING_TINT_UNIFORM..GLASS_RESTING_TINT_UNIFORM + 4],
&[0.0, 0.0, 0.0, 0.0]
);
assert_eq!(uniforms[32], 0.0);
assert_eq!(uniforms[26], 0.0);
assert_eq!(&uniforms[108..110], &[1.0, 1.0]);
assert_eq!(&uniforms[2..6], &[60.0, 36.0, 96.0, 52.0]);
}
#[test]
fn surface_refraction_is_explicit_and_reaches_the_shader() {
for glass in [Glass::regular(), Glass::lens()] {
assert_eq!(glass.refraction, GlassRefraction::Radial);
let surface = glass.surface_refraction(31.0);
assert_eq!(
surface.refraction,
GlassRefraction::Surface { reach_dp: 31.0 }
);
let shader = terminal_shader(surface.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics::default(),
));
assert_eq!(shader.uniforms()[GLASS_REFRACTION_MODE_UNIFORM], 1.0);
assert_eq!(shader.uniforms()[GLASS_EDGE_REFRACTION_REACH_UNIFORM], 31.0);
assert!(
!shader
.overrides()
.iter()
.any(|(name, value)| *name == "GLASS_DIRECTIONAL_REFRACTION_OFF"
&& *value != 0.0)
);
}
}
#[test]
fn edge_refraction_preserves_face_zoom_and_scales_outward_reach_with_contact() {
assert_eq!(GlassRefraction::default(), GlassRefraction::Radial);
let glass = Glass::lens()
.surface_refraction(31.0)
.edge_refraction(12.0)
.optical_zoom(1.2);
assert_eq!(
glass.refraction,
GlassRefraction::EdgeLens { reach_dp: 12.0 }
);
for (activity, press, reach) in [(1.0, 1.0, 12.0), (0.5, 0.5, 3.0), (0.0, 1.0, 0.0)] {
let shader = terminal_shader(glass.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics {
activity: Some(activity),
press_depth: Some(press),
..Default::default()
},
));
assert_eq!(shader.uniforms()[GLASS_REFRACTION_MODE_UNIFORM], 2.0);
assert_eq!(
shader.uniforms()[GLASS_EDGE_REFRACTION_REACH_UNIFORM],
reach
);
assert_eq!(
shader.uniforms()[GLASS_OPTICAL_ZOOM_UNIFORM],
1.0 + 0.2 * activity
);
}
assert_eq!(
glass.surface_refraction(31.0).refraction,
GlassRefraction::Surface { reach_dp: 31.0 }
);
}
#[test]
fn edge_refraction_captures_the_farthest_spectral_ray() {
for (reach, dispersion, required) in
[(20.0, 1.0, 101.67), (0.0, 1.0, 42.78), (20.0, 0.0, 60.88)]
{
let shader = terminal_shader(
Glass::lens()
.edge_refraction(reach)
.dispersion(dispersion)
.backdrop_effect(&light_colors(), 3.0, GlassDynamics::default()),
);
assert!(shader.input_padding() >= required);
}
}
#[test]
fn directional_refraction_rejects_invalid_reach_at_both_public_entry_points() {
for input in [-1.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 0.0, 12.0] {
let expected = if input.is_finite() {
input.max(0.0)
} else {
0.0
};
for (built, direct, normalized) in [
(
Glass::lens().edge_refraction(input),
GlassRefraction::EdgeLens { reach_dp: input },
GlassRefraction::EdgeLens { reach_dp: expected },
),
(
Glass::regular().surface_refraction(input),
GlassRefraction::Surface { reach_dp: input },
GlassRefraction::Surface { reach_dp: expected },
),
] {
assert_eq!(built.refraction, normalized);
let direct = Glass {
refraction: direct,
..built
};
let shader = terminal_shader(direct.backdrop_effect(
&light_colors(),
1.0,
GlassDynamics::default(),
));
assert_eq!(
shader.uniforms()[GLASS_EDGE_REFRACTION_REACH_UNIFORM],
expected
);
}
}
}
#[test]
fn surface_reach_is_independent_of_depth_and_tracks_contact() {
for depth in [4.0, 15.5, 30.0] {
for (activity, press, reach) in [(1.0, 1.0, 31.0), (0.5, 0.5, 7.75), (0.0, 1.0, 0.0)] {
let shader = terminal_shader(
Glass::regular()
.surface_refraction(31.0)
.refraction_depth_dp(depth)
.backdrop_effect(
&light_colors(),
3.0,
GlassDynamics {
activity: Some(activity),
press_depth: Some(press),
..Default::default()
},
),
);
assert_eq!(
shader.uniforms()[GLASS_EDGE_REFRACTION_REACH_UNIFORM],
reach
);
assert_eq!(
shader.uniforms()[GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM],
depth * activity * press
);
assert!(shader.input_padding() >= 31.0);
}
}
}
#[test]
fn resting_surface_tint_survives_zero_optical_activity() {
let tint = Color::BLACK.with_alpha(0.11);
let RenderEffect::Shader { shader } = Glass::lens()
.no_clip()
.resolve(&light_colors())
.backdrop_effect(
1.0,
GlassDynamics {
activity: Some(0.0),
resting_tint: Some(tint),
..Default::default()
},
)
else {
panic!("resting surface must use the shared wcKSRD shader");
};
assert_eq!(
&shader.uniforms()[GLASS_RESTING_TINT_UNIFORM..GLASS_RESTING_TINT_UNIFORM + 4],
&[tint.r(), tint.g(), tint.b(), tint.a()]
);
assert_eq!(shader.uniforms()[GLASS_ACTIVITY_UNIFORM], 0.0);
}
#[test]
fn full_optical_activity_preserves_the_resolved_material() {
let resolved = Glass::lens()
.refraction_depth(0.72)
.refraction_curve(0.8)
.dispersion(0.42)
.blur_radius(3.0)
.resolve(&light_colors());
let shader = terminal_shader(resolved.backdrop_effect(
2.0,
GlassDynamics {
activity: Some(1.0),
..Default::default()
},
));
let uniforms = shader.uniforms();
assert_eq!(uniforms[GLASS_ACTIVITY_UNIFORM], 1.0);
assert_eq!(uniforms[9], 0.72);
assert_eq!(
uniforms[GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM],
0.0
);
assert_eq!(uniforms[GLASS_REFRACTION_CURVE_UNIFORM], 0.8);
assert_eq!(uniforms[GLASS_DISPERSION_UNIFORM], 0.42);
assert_eq!(uniforms[GLASS_TRANSMISSION_REFRACTION_UNIFORM], 1.0);
assert_eq!(uniforms[GLASS_BLUR_RADIUS_UNIFORM], 0.0);
}
#[test]
fn morph_geometry_and_incompressible_strain_are_packed() {
let deformation = GlassDeformation::incompressible((0.0, 2.0), 1.25);
let morph = GlassMorph {
node_size: (78.0, 59.0),
primary: (39.0, 29.5, 58.0, 39.0, -1.0),
shapes: vec![(70.0, 29.5, 40.0, 40.0, -1.0)],
glue: 8.0,
wobble_amplitude: 1.0,
wobble_phase: 0.5,
bulge_amplitude: 2.0,
bulge_direction: 0.25,
ellipse_blend: 0.3,
capsule_smoothing_dp: 4.0,
deformation: Some(deformation),
zoom_anchor: (0.0, 0.0),
};
let RenderEffect::Shader { shader } = Glass::lens()
.no_clip()
.resolve(&light_colors())
.backdrop_effect(
1.0,
GlassDynamics {
morph: Some(morph),
..Default::default()
},
)
else {
panic!("morph must use the shared wcKSRD shader");
};
let uniforms = shader.uniforms();
assert_eq!(&uniforms[0..6], &[78.0, 59.0, 39.0, 29.5, 58.0, 39.0]);
assert_eq!(uniforms[30], 1.0);
assert_eq!(&uniforms[106..110], &[0.0, 1.0, 1.25, 0.8]);
assert_eq!(uniforms[110], 0.3);
assert_eq!(
uniforms[cranpose_ui_graphics::liquid_glass::GLASS_CAPSULE_SMOOTHING_UNIFORM],
4.0
);
assert!(shader.output_padding() > 0.0);
}
#[test]
fn capsule_smoothing_is_finite_nonnegative_and_invalidates_the_morph() {
for value in [-1.0, 0.0, 4.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let morph = GlassMorph {
node_size: (120.0, 80.0),
primary: (60.0, 40.0, 111.0, 70.0, -1.0),
capsule_smoothing_dp: value,
..Default::default()
};
let expected = if value.is_finite() {
value.max(0.0)
} else {
0.0
};
let shader = terminal_shader(Glass::lens().backdrop_effect(
&light_colors(),
3.0,
GlassDynamics {
morph: Some(morph.clone()),
..Default::default()
},
));
assert_eq!(
shader.uniforms()[cranpose_ui_graphics::GLASS_CAPSULE_SMOOTHING_UNIFORM],
expected
);
assert!(glass_morphs_match(&morph, &morph));
let changed = GlassMorph {
capsule_smoothing_dp: 5.0,
..morph.clone()
};
assert!(!glass_morphs_match(&morph, &changed));
}
}
#[test]
fn optical_builders_hold_their_own_legal_range() {
let glass = Glass::regular()
.fold_depth(-4.0)
.optical_zoom(0.5)
.rim_reflection(9.0);
assert_eq!(glass.fold_depth, 0.0, "a fold cannot be negative deep");
assert_eq!(glass.optical_zoom, 1.0, "a lens never shrinks its face");
assert_eq!(
glass.rim_reflection, 2.0,
"the rim tops out at twice the line"
);
let glass = Glass::regular()
.fold_depth(6.0)
.optical_zoom(1.4)
.rim_reflection(0.25);
assert_eq!(glass.fold_depth, 6.0);
assert_eq!(glass.optical_zoom, 1.4);
assert_eq!(glass.rim_reflection, 0.25);
}
#[test]
fn ink_recolor_records_its_color_at_a_clamped_strength() {
let red = Color::from_rgb_u8(255, 0, 0);
let glass = Glass::regular().ink_recolor(red, 3.0);
assert_eq!(glass.ink_recolor, Some((red, 1.0)));
let glass = Glass::regular().ink_recolor(red, -1.0);
assert_eq!(glass.ink_recolor, Some((red, 0.0)));
assert_eq!(Glass::regular().ink_recolor, None, "no recolor by default");
}
#[test]
fn a_touched_up_surface_amplifies_what_it_already_is() {
let dynamics = GlassDynamics::default().touched_up(1.0, None, (10.0, 4.0));
assert!(
dynamics.highlight_boost > 0.0,
"a pressed surface goes brighter"
);
assert!(dynamics.saturation_boost > 0.0, "and more saturated");
assert_eq!(
dynamics.touch,
Some((10.0, 4.0, 1.0)),
"with no finger the glow sits at the surface's own heart"
);
assert_eq!(
dynamics.resting_tint, None,
"a touch never repaints the surface with another color"
);
}
#[test]
fn a_touch_glow_follows_the_finger_and_accumulates_to_full() {
let dynamics = GlassDynamics::default()
.touched_up(0.5, Some((3.0, 7.0)), (10.0, 4.0))
.touched_up(0.8, Some((3.0, 7.0)), (10.0, 4.0));
let (x, y, intensity) = dynamics.touch.expect("a pressed surface glows");
assert_eq!(
(x, y),
(3.0, 7.0),
"the glow tracks the finger, not the center"
);
assert_eq!(intensity, 1.0, "carried press saturates at full");
}
#[test]
fn an_unpressed_surface_is_left_exactly_as_it_was() {
let resting = GlassDynamics::default();
let untouched = resting
.clone()
.touched_up(0.0, Some((3.0, 7.0)), (10.0, 4.0));
assert_eq!(untouched.highlight_boost, resting.highlight_boost);
assert_eq!(untouched.saturation_boost, resting.saturation_boost);
assert_eq!(untouched.touch, None, "no press, no glow");
}
#[test]
fn an_off_axis_strain_widens_the_support_by_the_affine_rows_times_the_half_extents() {
let resolved = Glass::regular()
.shape(LiquidShape::RoundedRect(0.0))
.blur_radius(0.0)
.shadow(false)
.no_clip()
.resolve(&light_colors());
let angle = 22.5f32.to_radians();
let morph = GlassMorph {
node_size: (600.0, 600.0),
primary: (300.0, 300.0, 200.0, 200.0, 0.0),
deformation: Some(GlassDeformation::incompressible(
(angle.cos(), angle.sin()),
2.0,
)),
..Default::default()
};
let RenderEffect::Shader { shader } = resolved.backdrop_effect(
1.0,
GlassDynamics {
activity: Some(1.0),
morph: Some(morph),
..Default::default()
},
) else {
panic!("an unblurred glass is one shader");
};
let support = shader
.output_support()
.expect("a morphing glass declares its support");
let half_x = 100.0 * (2.0 * angle.cos().powi(2) + 0.5 * angle.sin().powi(2))
+ 100.0 * (1.5 * angle.sin() * angle.cos());
assert!(
(half_x - 231.066).abs() < 0.01,
"the example's half extent is {half_x}"
);
assert!(
support.x <= 300.0 - half_x && support.x + support.width >= 300.0 + half_x,
"the support {support:?} must hold the strained square's x extent {half_x}"
);
let half_y = 100.0 * (1.5 * angle.sin() * angle.cos())
+ 100.0 * (2.0 * angle.sin().powi(2) + 0.5 * angle.cos().powi(2));
assert!(
(half_y - 125.0).abs() < 0.01,
"the example's y half extent is {half_y}"
);
assert!(
support.y <= 300.0 - half_y && support.y + support.height >= 300.0 + half_y,
"the support {support:?} must hold the strained square's y extent {half_y}"
);
}
#[test]
fn a_morphing_glass_declares_its_output_support_around_its_shapes_and_their_reach() {
let resolved = Glass::regular()
.shape(LiquidShape::RoundedRect(12.0))
.blur_radius(0.0)
.shadow(true)
.no_clip()
.resolve(&light_colors());
let morph = GlassMorph {
node_size: (280.0, 160.0),
primary: (140.0, 80.0, 120.0, 40.0, -1.0),
shapes: vec![(230.0, 80.0, 40.0, 40.0, -1.0)],
glue: 20.0,
wobble_amplitude: 3.0,
bulge_amplitude: 8.0,
deformation: Some(GlassDeformation::incompressible((1.0, 0.0), 1.25)),
..Default::default()
};
let dynamics = GlassDynamics {
activity: Some(1.0),
morph: Some(morph.clone()),
..Default::default()
};
let RenderEffect::Shader { shader } = resolved.backdrop_effect(2.0, dynamics) else {
panic!("an unblurred glass is one shader");
};
let support = shader
.output_support()
.expect("a morphing glass declares its support");
let shape_reach = (230.0 + 20.0) - (140.0 + 60.0);
let shadow_reach = resolved.shadow_radius
+ resolved.shadow_offset_y.abs()
+ resolved.shadow_spread.max(0.0);
let field_reach = 3.0 * 2.0 + 8.0 + shape_reach + 40.0 + shadow_reach + 4.0;
let smallest_strain = 0.8;
let reach = field_reach / smallest_strain;
let contains = |x: f32, y: f32| {
support.x <= x
&& support.y <= y
&& support.x + support.width >= x
&& support.y + support.height >= y
};
for (cx, cy) in [(-1.0, -1.0), (1.0, -1.0), (-1.0, 1.0), (1.0, 1.0)] {
let corner = ((60.0 + reach) * cx, (20.0 + reach) * cy);
let stretched = (140.0 + corner.0 * 1.25, 80.0 + corner.1 * 0.8);
assert!(
contains(stretched.0, stretched.1 - resolved.shadow_offset_y.abs())
&& contains(stretched.0, stretched.1 + resolved.shadow_offset_y.abs()),
"{support:?} must hold the strained, dilated corner {stretched:?}"
);
}
assert!(
contains(230.0 + 20.0 + reach, 80.0),
"the glued shape's reach is inside"
);
assert!(shader.output_padding() >= field_reach);
let RenderEffect::Shader { shader } =
resolved.backdrop_effect(2.0, GlassDynamics::default())
else {
panic!("an unblurred glass is one shader");
};
assert_eq!(
shader.output_support(),
None,
"cover-mode glass fills its rect"
);
}
}
#[cfg(test)]
mod transparency_tests {
use super::*;
#[test]
fn less_transparency_makes_glass_a_flat_surface() {
let colors = crate::theme::LiquidColors::light(Color::from_rgb_u8(0, 122, 255));
let flat = Glass::regular()
.resolve(&colors)
.without_transparency(&colors);
assert_eq!(flat.tint, colors.surface);
assert_eq!(flat.blur_radius_dp, 0.0);
assert_eq!(flat.refraction_depth, 0.0);
assert_eq!(flat.saturation, 1.0);
assert!(flat.edge_spectrum.is_none());
let clear = Glass::regular().resolve(&colors);
assert!(clear.blur_radius_dp > 0.0, "plain glass keeps its blur");
}
}