1use std::{
7 cell::{Cell, RefCell},
8 sync::OnceLock,
9};
10
11use cranpose_ui::{Modifier, current_density};
12use cranpose_ui_graphics::{
13 Color, GLASS_ACTIVITY_UNIFORM, GLASS_ADAPTIVE_FROST_UNIFORM, GLASS_ADAPTIVE_TONE_UNIFORM,
14 GLASS_BLUR_RADIUS_UNIFORM, GLASS_DISPERSION_UNIFORM, GLASS_EDGE_REFRACTION_REACH_UNIFORM,
15 GLASS_EDGE_RETURN_DEPTH_UNIFORM, GLASS_EDGE_SPECTRUM_UNIFORM, GLASS_EFFECT_DENSITY_UNIFORM,
16 GLASS_FACE_LIGHTING_OFF_UNIFORM, GLASS_FACE_RESPONSE_UNIFORM, GLASS_FOLD_DEPTH_UNIFORM,
17 GLASS_INNER_SHADOW_UNIFORM, GLASS_KEY_FILL_UNIFORM, GLASS_LAYER_CLIPPED_UNIFORM,
18 GLASS_LAYERED_RETURN_UNIFORM, GLASS_LIGHT_DIRECTION_UNIFORM, GLASS_MENISCUS_ABSORPTION_UNIFORM,
19 GLASS_OPTICAL_PROJECTION_UNIFORM, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
20 GLASS_OPTICAL_ZOOM_UNIFORM, GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
21 GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM, GLASS_REFRACTION_CURVE_UNIFORM,
22 GLASS_REFRACTION_MODE_UNIFORM, GLASS_RESTING_EDGE_SHARPNESS_UNIFORM,
23 GLASS_RESTING_TINT_UNIFORM, GLASS_TOUCH_RADIUS_UNIFORM, GLASS_TRANSMISSION_REFRACTION_UNIFORM,
24 GraphicsLayer, LIQUID_GLASS_WGSL, LayerShape, Rect, RenderEffect, RoundedCornerShape,
25 RuntimeShader, TileMode, liquid_glass_runtime_effect, specialize_liquid_glass,
26};
27
28use crate::{appearance::GlassTintAmount, theme::LiquidColors};
29
30thread_local! {
31 static GLASS_LIGHT_RETURN: Cell<(f32, f32)> = const { Cell::new((0.0, 1.0)) };
32}
33
34pub fn set_glass_light_direction(direction: (f32, f32)) {
37 GLASS_LIGHT_RETURN.with(|cell| cell.set(direction));
38}
39
40pub fn glass_light_direction() -> (f32, f32) {
42 GLASS_LIGHT_RETURN.with(Cell::get)
43}
44
45const CAPSULE_CLIP_RADIUS: f32 = 1.0e6;
46
47const CAPSULE_SHADER_RADIUS: f32 = -1.0;
48
49const WCKSRD_OPTICAL_BLUR_RADIUS_PX: f32 = 2.0;
50
51#[derive(Clone, Copy, Debug, PartialEq, Default)]
53pub enum LiquidShape {
54 #[default]
56 Capsule,
57 RoundedRect(f32),
59 Circle,
61}
62
63impl LiquidShape {
64 pub fn clip_shape(&self) -> RoundedCornerShape {
66 match self {
67 LiquidShape::Capsule | LiquidShape::Circle => {
68 RoundedCornerShape::uniform(CAPSULE_CLIP_RADIUS)
69 }
70 LiquidShape::RoundedRect(radius) => RoundedCornerShape::uniform(*radius),
71 }
72 }
73
74 pub fn layer_shape(&self) -> LayerShape {
76 LayerShape::Rounded(self.clip_shape())
77 }
78
79 fn shader_radius_px(&self, density: f32) -> f32 {
80 match self {
81 LiquidShape::Capsule | LiquidShape::Circle => CAPSULE_SHADER_RADIUS,
82 LiquidShape::RoundedRect(radius) => radius * density,
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
90pub enum GlassVariant {
91 #[default]
93 Regular,
94 Clear,
96 Lens,
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Default)]
104pub enum GlassRefraction {
105 #[default]
107 Radial,
108 Surface {
110 reach_dp: f32,
112 },
113 EdgeLens {
117 reach_dp: f32,
119 },
120 LayeredSurface {
124 reach_dp: f32,
126 return_reach_dp: f32,
128 return_depth_dp: f32,
130 },
131}
132
133impl GlassRefraction {
134 fn shader_parameters(self) -> (f32, f32) {
135 match self {
136 Self::Radial => (0.0, 0.0),
137 Self::Surface { reach_dp } => (1.0, finite_nonnegative(reach_dp)),
138 Self::EdgeLens { reach_dp } => (2.0, finite_nonnegative(reach_dp)),
139 Self::LayeredSurface { reach_dp, .. } => (3.0, finite_nonnegative(reach_dp)),
140 }
141 }
142}
143
144fn finite_nonnegative(reach_dp: f32) -> f32 {
145 if reach_dp.is_finite() {
146 reach_dp.max(0.0)
147 } else {
148 0.0
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq)]
156pub struct GlassShadow {
157 pub color: Color,
158 pub radius: f32,
159 pub offset_y: f32,
160 pub spread: f32,
161}
162
163impl GlassShadow {
164 pub fn new(color: Color, radius: f32, offset_y: f32, spread: f32) -> Self {
165 Self {
166 color,
167 radius: radius.max(0.0),
168 offset_y,
169 spread,
170 }
171 }
172
173 fn uniforms(self) -> Option<[f32; 8]> {
174 let values = [
175 self.color.r(),
176 self.color.g(),
177 self.color.b(),
178 self.color.a(),
179 self.radius,
180 self.offset_y,
181 self.spread,
182 1.0,
183 ];
184 (values.iter().all(|value| value.is_finite())
185 && self.radius >= 0.0
186 && (0.0..=1.0).contains(&self.color.a()))
187 .then_some(values)
188 }
189}
190
191#[derive(Clone, Debug, PartialEq, Default)]
194pub struct GlassDynamics {
195 pub contact_lighting: bool,
197 pub refraction: Option<GlassRefraction>,
200 pub shadow: Option<GlassShadow>,
202 pub ring_shadow: Option<(GlassShadow, f32)>,
206 pub edge_return_depth_dp: Option<f32>,
209 pub optical_projection: Option<(f32, f32)>,
213 pub activity: Option<f32>,
217 pub resting_tint: Option<Color>,
220 pub highlight_boost: f32,
222 pub saturation_boost: f32,
225 pub tint_alpha_multiplier: Option<f32>,
228 pub tint_crossfade: Option<(Color, f32)>,
232 pub morph: Option<GlassMorph>,
235 pub touch: Option<(f32, f32, f32)>,
239 pub touch_radius_dp: Option<f32>,
241 pub press_depth: Option<f32>,
247}
248
249impl GlassDynamics {
250 fn projection(&self) -> (f32, f32) {
251 self.optical_projection
252 .filter(|(x, y)| x.is_finite() && y.is_finite() && *x > 0.0 && *y > 0.0)
253 .unwrap_or((1.0, 1.0))
254 }
255
256 pub fn touched_up(
263 mut self,
264 press: f32,
265 finger: Option<(f32, f32)>,
266 center: (f32, f32),
267 ) -> Self {
268 let press = press.clamp(0.0, 1.0);
269 if press <= 0.0 {
270 return self;
271 }
272 self.highlight_boost += 0.85 * press;
273 self.saturation_boost += 0.45 * press;
274 let (fx, fy) = finger.unwrap_or(center);
275 let carried = self.touch.map_or(0.0, |(_, _, i)| i);
276 self.touch = Some((fx, fy, (carried + press).clamp(0.0, 1.0)));
277 self
278 }
279}
280
281fn foreground_is_dark(foreground: Color) -> bool {
282 let foreground_luma =
283 0.2126 * foreground.r() + 0.7152 * foreground.g() + 0.0722 * foreground.b();
284 foreground_luma < 0.5
285}
286
287fn boost_tint_saturation(tint: Color, boost: f32) -> Color {
288 if boost.abs() <= f32::EPSILON {
289 return tint;
290 }
291 let luma = 0.2126 * tint.r() + 0.7152 * tint.g() + 0.0722 * tint.b();
292 let saturation = (1.0 + boost).max(0.0);
293 Color::rgba(
294 (luma + (tint.r() - luma) * saturation).clamp(0.0, 1.0),
295 (luma + (tint.g() - luma) * saturation).clamp(0.0, 1.0),
296 (luma + (tint.b() - luma) * saturation).clamp(0.0, 1.0),
297 tint.a(),
298 )
299}
300
301pub(crate) fn neutral_surface_tint(foreground: Color, light_alpha: f32, dark_alpha: f32) -> Color {
302 if foreground_is_dark(foreground) {
303 Color::BLACK.with_alpha(light_alpha.clamp(0.0, 1.0))
304 } else {
305 Color::WHITE.with_alpha(dark_alpha.clamp(0.0, 1.0))
306 }
307}
308
309#[derive(Clone, Debug, PartialEq, Default)]
320pub struct GlassMorph {
321 pub node_size: (f32, f32),
327 pub primary: (f32, f32, f32, f32, f32),
328 pub shapes: Vec<(f32, f32, f32, f32, f32)>,
330 pub glue: f32,
332 pub wobble_amplitude: f32,
334 pub wobble_phase: f32,
335 pub bulge_amplitude: f32,
339 pub bulge_direction: f32,
340 pub ellipse_blend: f32,
344 pub capsule_smoothing_dp: f32,
349 pub deformation: Option<GlassDeformation>,
353 pub zoom_anchor: (f32, f32),
359}
360
361#[derive(Clone, Copy, Debug, PartialEq)]
365pub struct GlassDeformation {
366 axis: (f32, f32),
367 along: f32,
368}
369
370impl GlassDeformation {
371 pub fn incompressible(axis: (f32, f32), along: f32) -> Self {
372 let length = (axis.0 * axis.0 + axis.1 * axis.1).sqrt();
373 let axis = if length > f32::EPSILON {
374 (axis.0 / length, axis.1 / length)
375 } else {
376 (1.0, 0.0)
377 };
378 Self {
379 axis,
380 along: along.max(f32::EPSILON),
381 }
382 }
383
384 pub fn axis(self) -> (f32, f32) {
385 self.axis
386 }
387
388 pub fn along(self) -> f32 {
389 self.along
390 }
391
392 pub fn across(self) -> f32 {
393 1.0 / self.along
394 }
395}
396
397impl GlassMorph {
398 pub const MAX_SHAPES: usize = 8;
400}
401
402#[derive(Clone, Copy, Debug, PartialEq)]
406pub struct GlassContourHighlight {
407 pub height_dp: f32,
409 pub offset_dp: f32,
411 pub amount: f32,
413 pub color_bias: f32,
415 pub angle_radians: f32,
417 pub spread_radians: f32,
419}
420
421impl GlassContourHighlight {
422 fn uniforms(self) -> Option<[f32; 6]> {
423 let values = [
424 self.height_dp,
425 self.offset_dp,
426 self.amount,
427 self.color_bias,
428 self.angle_radians,
429 self.spread_radians,
430 ];
431 (values.iter().all(|value| value.is_finite())
432 && self.height_dp > 0.0
433 && self.spread_radians > 0.0)
434 .then_some(values)
435 }
436}
437
438#[derive(Clone, Copy, Debug, PartialEq)]
441pub struct GlassSpecularHighlight {
442 pub height_dp: f32,
444 pub angle_radians: f32,
446 pub spread_radians: f32,
448 pub curvature: f32,
450 pub diffuse_amount: f32,
452 pub diffuse_height_dp: f32,
454 pub diffuse_spread_radians: f32,
456 pub saturation: f32,
458 pub luma_gain: f32,
460 pub color_bias: f32,
462}
463
464impl GlassSpecularHighlight {
465 fn uniforms(self) -> Option<[f32; 10]> {
466 let values = [
467 self.height_dp,
468 self.angle_radians,
469 self.spread_radians,
470 self.curvature,
471 self.diffuse_amount,
472 self.diffuse_height_dp,
473 self.diffuse_spread_radians,
474 self.saturation,
475 self.luma_gain,
476 self.color_bias,
477 ];
478 (values.iter().all(|value| value.is_finite())
479 && self.height_dp > 0.0
480 && self.spread_radians > 0.0
481 && self.curvature > 0.0
482 && self.diffuse_height_dp > 0.0
483 && self.diffuse_spread_radians > 0.0)
484 .then_some(values)
485 }
486}
487
488#[derive(Clone, Copy, Debug, PartialEq)]
491pub struct GlassKeyFill {
492 pub height_dp: f32,
494 pub curvature: f32,
496 pub angle_radians: f32,
498 pub saturation: f32,
500 pub luma_gain: f32,
502 pub offset: f32,
504 pub scale_with_surface: bool,
506}
507
508impl GlassKeyFill {
509 fn uniforms(self) -> Option<[f32; 7]> {
510 let values = [
511 self.height_dp,
512 self.curvature,
513 self.angle_radians,
514 self.saturation,
515 self.luma_gain,
516 self.offset,
517 f32::from(self.scale_with_surface),
518 ];
519 (values.iter().all(|value| value.is_finite())
520 && self.height_dp > 0.0
521 && self.curvature > 0.0)
522 .then_some(values)
523 }
524}
525
526#[derive(Clone, Copy, Debug, PartialEq)]
529pub struct GlassSpectrum {
530 pub angle_radians: f32,
533 pub step_dp: f32,
535 pub vertical_scale: f32,
537 pub opacity_near: f32,
539 pub opacity_far: f32,
541 pub fade_depth_dp: f32,
543 pub extent_dp: f32,
545}
546
547impl GlassSpectrum {
548 fn uniforms(self) -> Option<[f32; 8]> {
549 let values = [
550 self.angle_radians,
551 self.step_dp,
552 self.vertical_scale,
553 self.opacity_near,
554 self.opacity_far,
555 self.fade_depth_dp,
556 self.extent_dp,
557 1.0,
558 ];
559 (values.iter().all(|value| value.is_finite())
560 && self.vertical_scale >= 0.0
561 && (0.0..=1.0).contains(&self.opacity_near)
562 && (0.0..=1.0).contains(&self.opacity_far)
563 && self.fade_depth_dp >= 0.0
564 && self.extent_dp >= 0.0)
565 .then_some(values)
566 }
567
568 fn capture_reach(self) -> f32 {
569 self.step_dp.abs() * 3.0 * self.vertical_scale.max(1.0)
570 }
571}
572
573#[derive(Clone, Copy, Debug, PartialEq)]
577pub struct GlassFaceResponse {
578 pub gain: f32,
580 pub start_dp: f32,
582 pub end_dp: f32,
585 pub illumination: f32,
587}
588
589impl GlassFaceResponse {
590 fn uniforms(self) -> Option<[f32; 4]> {
591 let values = [
592 1.0 - self.gain,
593 self.start_dp,
594 self.end_dp,
595 self.illumination,
596 ];
597 (values.iter().all(|value| value.is_finite())
598 && (0.0..=1.0).contains(&self.gain)
599 && self.start_dp >= 0.0
600 && self.end_dp >= self.start_dp)
601 .then_some(values)
602 }
603}
604
605#[derive(Clone, Copy, Debug, PartialEq)]
609pub struct GlassFaceTone {
610 pub black: f32,
612 pub white: f32,
614 pub saturation: f32,
616 pub max_luminance: f32,
618}
619
620impl GlassFaceTone {
621 fn uniforms(self) -> Option<[f32; 5]> {
622 let values = [
623 self.black,
624 self.white,
625 self.saturation,
626 self.max_luminance,
627 1.0,
628 ];
629 (values.iter().all(|value| value.is_finite())
630 && (0.0..=1.0).contains(&self.black)
631 && self.white > self.black
632 && self.saturation >= 0.0
633 && (0.0..=1.0).contains(&self.max_luminance))
634 .then_some(values)
635 }
636}
637
638#[derive(Clone, Debug, PartialEq)]
641pub struct Glass {
642 pub face_tone: Option<GlassFaceTone>,
644 pub variant: GlassVariant,
645 pub shape: LiquidShape,
646 pub tint: Option<Color>,
648 pub tint_transmission: Option<f32>,
650 pub tint_amount: Option<GlassTintAmount>,
652 pub pane_blur_radius: f32,
654 pub blur_radius: Option<f32>,
656 pub backdrop_blur: Option<(f32, f32)>,
658 pub saturation: Option<f32>,
660 pub refraction_depth: f32,
664 pub refraction_depth_dp: Option<f32>,
668 pub refraction: GlassRefraction,
670 pub refraction_curve: f32,
672 pub dispersion: f32,
674 pub edge_spectrum: Option<GlassSpectrum>,
676 pub transmission_refraction: f32,
680 pub meniscus_absorption: f32,
683 pub fold_depth: f32,
687 pub optical_zoom: f32,
690 pub rim_reflection: f32,
693 pub ink_recolor: Option<(Color, f32)>,
697 pub highlight: f32,
699 pub face_lighting: bool,
701 pub key_fill: Option<GlassKeyFill>,
703 pub contour_highlight: Option<GlassContourHighlight>,
705 pub specular_highlight: Option<GlassSpecularHighlight>,
707 pub face_response: Option<GlassFaceResponse>,
709 pub inner_shadow: Option<GlassShadow>,
711 pub lift: Option<f32>,
714 pub contrast: Option<f32>,
719 pub shadow: bool,
721 pub shadow_style: Option<GlassShadow>,
723 pub clip: bool,
726 pub foreground: Option<Color>,
729 pub adaptive_frost: f32,
731 pub adaptive_tone: bool,
734 pub resting_edge_sharpness: f32,
736}
737
738impl Glass {
739 pub fn regular() -> Self {
741 Self {
742 variant: GlassVariant::Regular,
743 face_tone: None,
744 shape: LiquidShape::Capsule,
745 tint: None,
746 tint_transmission: None,
747 tint_amount: None,
748 pane_blur_radius: 8.0,
749 blur_radius: None,
750 backdrop_blur: None,
751 saturation: None,
752 refraction_depth: 0.0,
753 refraction_depth_dp: None,
754 refraction: GlassRefraction::Radial,
755 refraction_curve: 0.25,
756 dispersion: 0.0,
757 edge_spectrum: None,
758 transmission_refraction: 0.0,
759 meniscus_absorption: 1.0,
760 fold_depth: 0.0,
761 optical_zoom: 1.0,
762 rim_reflection: 1.0,
763 ink_recolor: None,
764 highlight: 0.9,
765 face_lighting: true,
766 key_fill: None,
767 contour_highlight: None,
768 specular_highlight: None,
769 face_response: None,
770 inner_shadow: None,
771 lift: None,
772 contrast: None,
773 shadow: true,
774 shadow_style: None,
775 clip: true,
776 foreground: None,
777 adaptive_frost: 0.65,
778 adaptive_tone: false,
779 resting_edge_sharpness: 0.0,
780 }
781 }
782
783 pub fn clear() -> Self {
784 Self {
785 variant: GlassVariant::Clear,
786 ..Self::regular()
787 }
788 }
789
790 pub fn lens() -> Self {
793 Self {
794 variant: GlassVariant::Lens,
795 tint: Some(Color::rgba(1.0, 1.0, 1.0, 0.07)),
796 refraction_depth: 0.34,
797 refraction_curve: 1.0,
798 dispersion: 0.30,
799 transmission_refraction: 1.0,
800 highlight: 1.15,
801 adaptive_frost: 0.0,
802 ..Self::regular()
803 }
804 }
805
806 pub fn face_lighting(mut self, enabled: bool) -> Self {
808 self.face_lighting = enabled;
809 self
810 }
811
812 pub fn resting_edge_sharpness(mut self, sharpness: f32) -> Self {
815 self.resting_edge_sharpness = sharpness;
816 self
817 }
818
819 pub fn key_fill(mut self, lighting: GlassKeyFill) -> Self {
822 self.key_fill = Some(lighting);
823 self
824 }
825
826 pub fn face_response(mut self, response: GlassFaceResponse) -> Self {
828 self.face_response = Some(response);
829 self
830 }
831
832 pub fn inner_shadow(mut self, shadow: GlassShadow) -> Self {
836 self.inner_shadow = shadow.uniforms().map(|_| shadow);
837 self
838 }
839
840 pub fn shape(mut self, shape: LiquidShape) -> Self {
841 self.shape = shape;
842 self
843 }
844
845 pub fn tint(mut self, tint: Color) -> Self {
846 self.tint = Some(tint);
847 self
848 }
849
850 pub fn transmission_tint(mut self, tint: Color, black: f32) -> Self {
852 if black.is_finite() {
853 self.tint = Some(tint);
854 self.tint_transmission = Some(black.clamp(0.0, 1.0));
855 }
856 self
857 }
858
859 pub fn blur_radius(mut self, radius_dp: f32) -> Self {
860 self.blur_radius = Some(radius_dp);
861 self
862 }
863
864 pub fn saturation(mut self, saturation: f32) -> Self {
865 self.saturation = Some(saturation);
866 self
867 }
868
869 pub fn refraction_depth(mut self, fraction: f32) -> Self {
870 self.refraction_depth = fraction.clamp(0.0, 2.0);
871 self.refraction_depth_dp = None;
872 self
873 }
874
875 pub fn refraction_depth_dp(mut self, depth_dp: f32) -> Self {
878 self.refraction_depth_dp = Some(depth_dp.max(0.0));
879 self
880 }
881
882 pub fn surface_refraction(mut self, reach_dp: f32) -> Self {
887 self.refraction = GlassRefraction::Surface {
888 reach_dp: finite_nonnegative(reach_dp),
889 };
890 self
891 }
892
893 pub fn edge_spectrum(mut self, spectrum: GlassSpectrum) -> Self {
896 self.edge_spectrum = spectrum.uniforms().map(|_| spectrum);
897 self
898 }
899
900 pub fn edge_refraction(mut self, reach_dp: f32) -> Self {
904 self.refraction = GlassRefraction::EdgeLens {
905 reach_dp: finite_nonnegative(reach_dp),
906 };
907 self
908 }
909
910 pub fn refraction_curve(mut self, curve: f32) -> Self {
911 self.refraction_curve = curve.clamp(0.05, 1.0);
912 self
913 }
914
915 pub fn dispersion(mut self, strength: f32) -> Self {
916 self.dispersion = strength.clamp(0.0, 1.0);
917 self
918 }
919
920 pub fn transmission_refraction(mut self, strength: f32) -> Self {
921 self.transmission_refraction = strength.clamp(0.0, 1.0);
922 self
923 }
924
925 pub fn meniscus_absorption(mut self, strength: f32) -> Self {
926 self.meniscus_absorption = strength.clamp(0.0, 1.0);
927 self
928 }
929
930 pub fn fold_depth(mut self, depth_dp: f32) -> Self {
932 self.fold_depth = depth_dp.max(0.0);
933 self
934 }
935
936 pub fn optical_zoom(mut self, zoom: f32) -> Self {
938 self.optical_zoom = zoom.max(1.0);
939 self
940 }
941
942 pub fn rim_reflection(mut self, reflectivity: f32) -> Self {
944 self.rim_reflection = reflectivity.clamp(0.0, 2.0);
945 self
946 }
947
948 pub fn ink_recolor(mut self, color: Color, strength: f32) -> Self {
951 self.ink_recolor = Some((color, strength.clamp(0.0, 1.0)));
952 self
953 }
954
955 pub fn highlight(mut self, highlight: f32) -> Self {
956 self.highlight = highlight;
957 self
958 }
959
960 pub fn lift(mut self, lift: f32) -> Self {
963 self.lift = Some(lift);
964 self
965 }
966
967 pub fn contrast(mut self, contrast: f32) -> Self {
970 self.contrast = Some(contrast.max(0.05));
971 self
972 }
973
974 pub fn adaptive_frost(mut self, foreground: Color, strength: f32) -> Self {
975 self.foreground = Some(foreground);
976 self.adaptive_frost = strength.clamp(0.0, 1.0);
977 self
978 }
979
980 pub fn adaptive_tone(mut self, foreground: Color) -> Self {
986 self.face_tone = None;
987 self.foreground = Some(foreground);
988 self.adaptive_tone = true;
989 self.adaptive_frost = 0.0;
990 self
991 }
992
993 pub fn face_tone(mut self, tone: GlassFaceTone) -> Self {
996 self.face_tone = tone.uniforms().map(|_| tone);
997 self.adaptive_tone = false;
998 self.adaptive_frost = 0.0;
999 self
1000 }
1001
1002 pub fn shadow(mut self, shadow: bool) -> Self {
1003 self.shadow = shadow;
1004 self
1005 }
1006
1007 pub fn shadow_style(mut self, shadow: GlassShadow) -> Self {
1008 self.shadow_style = Some(shadow);
1009 self
1010 }
1011
1012 pub fn no_clip(mut self) -> Self {
1015 self.clip = false;
1016 self
1017 }
1018
1019 pub fn tint_amount(mut self, amount: GlassTintAmount) -> Self {
1022 self.tint_amount = Some(amount);
1023 self
1024 }
1025
1026 pub fn pane_blur_radius(mut self, radius: f32) -> Self {
1028 if radius.is_finite() && radius >= 0.0 {
1029 self.pane_blur_radius = radius;
1030 }
1031 self
1032 }
1033
1034 pub fn backdrop_blur(mut self, radius_dp: f32, opacity: f32) -> Self {
1038 self.backdrop_blur = (radius_dp.is_finite() && opacity.is_finite())
1039 .then_some((radius_dp.max(0.0), opacity.clamp(0.0, 1.0)));
1040 self
1041 }
1042
1043 fn default_blur_radius(&self) -> f32 {
1044 match self.variant {
1045 GlassVariant::Regular => 8.0,
1046 GlassVariant::Clear => 3.0,
1047 GlassVariant::Lens => 0.0,
1048 }
1049 }
1050
1051 fn default_saturation(&self) -> f32 {
1052 match self.variant {
1053 GlassVariant::Regular => 1.5,
1054 GlassVariant::Clear => 1.25,
1055 GlassVariant::Lens => 1.0,
1056 }
1057 }
1058
1059 pub fn backdrop_effect(
1063 &self,
1064 colors: &LiquidColors,
1065 density: f32,
1066 dynamics: GlassDynamics,
1067 ) -> RenderEffect {
1068 self.resolve(colors).backdrop_effect(density, dynamics)
1069 }
1070
1071 fn preferred_foreground(&self, colors: &LiquidColors) -> Color {
1072 self.foreground
1073 .filter(|color| {
1074 [color.r(), color.g(), color.b(), color.a()]
1075 .iter()
1076 .all(|value| value.is_finite())
1077 })
1078 .unwrap_or(colors.label)
1079 }
1080
1081 pub(crate) fn resolve(&self, colors: &LiquidColors) -> ResolvedGlass {
1082 let lift = self.lift.unwrap_or(match (self.variant, colors.is_dark) {
1083 (GlassVariant::Regular, false) => 0.42,
1084 (GlassVariant::Regular, true) => -0.38,
1085 (GlassVariant::Clear, false) => 0.12,
1086 (GlassVariant::Clear, true) => -0.12,
1087 (GlassVariant::Lens, false) => 0.10,
1088 (GlassVariant::Lens, true) => -0.08,
1089 });
1090 let foreground = self.preferred_foreground(colors);
1091 let shadow = self.shadow_style.unwrap_or_else(|| {
1092 GlassShadow::new(
1093 Color::BLACK.with_alpha(match (self.variant, colors.is_dark) {
1094 (GlassVariant::Lens, false) => 0.14,
1095 (GlassVariant::Lens, true) => 0.28,
1096 (_, false) => 0.16,
1097 (_, true) => 0.5,
1098 }),
1099 if self.variant == GlassVariant::Lens {
1100 10.0
1101 } else {
1102 22.0
1103 },
1104 if self.variant == GlassVariant::Lens {
1105 3.0
1106 } else {
1107 8.0
1108 },
1109 if self.variant == GlassVariant::Lens {
1110 -6.0
1111 } else {
1112 -2.0
1113 },
1114 )
1115 });
1116 ResolvedGlass {
1117 face_tone: self.face_tone,
1118 shape: self.shape,
1119 tint: self.tint.unwrap_or(colors.glass_tint),
1120 tint_transmission: self.tint_transmission,
1121 tint_amount: self.tint_amount,
1122 pane_blur_radius: self.pane_blur_radius,
1123 backdrop_blur: self.backdrop_blur,
1124 blur_radius_dp: self
1125 .blur_radius
1126 .unwrap_or_else(|| self.default_blur_radius()),
1127 saturation: self.saturation.unwrap_or_else(|| self.default_saturation()),
1128 refraction_depth: self.refraction_depth,
1129 refraction_depth_dp: self.refraction_depth_dp,
1130 refraction: self.refraction,
1131 refraction_curve: self.refraction_curve,
1132 dispersion: self.dispersion,
1133 edge_spectrum: self.edge_spectrum,
1134 transmission_refraction: self.transmission_refraction,
1135 meniscus_absorption: self.meniscus_absorption,
1136 fold_depth: self.fold_depth,
1137 optical_zoom: self.optical_zoom,
1138 rim_reflection: self.rim_reflection,
1139 ink_recolor: self.ink_recolor,
1140 highlight: self.highlight,
1141 face_lighting: self.face_lighting,
1142 key_fill: self.key_fill,
1143 contour_highlight: self.contour_highlight,
1144 specular_highlight: self.specular_highlight,
1145 face_response: self.face_response,
1146 inner_shadow: self.inner_shadow,
1147 lift,
1148 contrast: self.contrast.unwrap_or(match self.variant {
1149 GlassVariant::Lens => 1.0,
1150 _ => 1.03,
1151 }),
1152 shadow: self.shadow,
1153 clip: self.clip,
1154 foreground_luma: 0.2126 * foreground.r()
1155 + 0.7152 * foreground.g()
1156 + 0.0722 * foreground.b(),
1157 adaptive_frost: self.adaptive_frost,
1158 adaptive_tone: self.adaptive_tone,
1159 resting_edge_sharpness: finite_nonnegative(self.resting_edge_sharpness).min(1.0),
1160 rim_style: if self.variant == GlassVariant::Lens {
1161 1.0
1162 } else {
1163 0.0
1164 },
1165 shadow_color: shadow.color,
1166 shadow_radius: shadow.radius,
1167 shadow_offset_y: shadow.offset_y,
1168 shadow_spread: shadow.spread,
1169 }
1170 }
1171}
1172
1173impl Default for Glass {
1174 fn default() -> Self {
1175 Self::regular()
1176 }
1177}
1178
1179#[derive(Clone, Debug, PartialEq)]
1180pub(crate) struct ResolvedGlass {
1181 specular_highlight: Option<GlassSpecularHighlight>,
1182 face_tone: Option<GlassFaceTone>,
1183 resting_edge_sharpness: f32,
1184 pub shape: LiquidShape,
1185 pub tint: Color,
1186 tint_transmission: Option<f32>,
1187 tint_amount: Option<GlassTintAmount>,
1188 pane_blur_radius: f32,
1189 backdrop_blur: Option<(f32, f32)>,
1190 pub blur_radius_dp: f32,
1191 pub saturation: f32,
1192 pub refraction_depth: f32,
1193 pub refraction_depth_dp: Option<f32>,
1194 pub refraction: GlassRefraction,
1195 pub refraction_curve: f32,
1196 pub dispersion: f32,
1197 pub edge_spectrum: Option<GlassSpectrum>,
1198 pub transmission_refraction: f32,
1199 pub meniscus_absorption: f32,
1200 pub fold_depth: f32,
1201 pub optical_zoom: f32,
1202 pub rim_reflection: f32,
1203 pub ink_recolor: Option<(Color, f32)>,
1204 pub highlight: f32,
1205 pub face_lighting: bool,
1206 pub key_fill: Option<GlassKeyFill>,
1207 pub contour_highlight: Option<GlassContourHighlight>,
1209 pub face_response: Option<GlassFaceResponse>,
1211 pub lift: f32,
1212 pub inner_shadow: Option<GlassShadow>,
1214 pub contrast: f32,
1215 pub shadow: bool,
1216 pub clip: bool,
1217 pub rim_style: f32,
1218 pub foreground_luma: f32,
1219 pub adaptive_frost: f32,
1220 pub adaptive_tone: bool,
1221 pub shadow_color: Color,
1222 pub shadow_radius: f32,
1223 pub shadow_offset_y: f32,
1224 pub shadow_spread: f32,
1225}
1226
1227fn set_edge_return_depth(shader: &mut RuntimeShader, depth: Option<f32>) {
1228 if let Some(depth) = depth.filter(|depth| depth.is_finite() && *depth >= 0.0) {
1229 shader.set_float2(GLASS_EDGE_RETURN_DEPTH_UNIFORM, depth, 1.0);
1230 }
1231}
1232
1233impl ResolvedGlass {
1234 pub(crate) fn without_transparency(mut self, colors: &LiquidColors) -> Self {
1238 self.tint = colors.surface;
1239 self.tint_transmission = None;
1240 self.tint_amount = None;
1241 self.backdrop_blur = None;
1242 self.face_tone = None;
1243 self.contour_highlight = None;
1244 self.specular_highlight = None;
1245 self.adaptive_tone = false;
1246 self.blur_radius_dp = 0.0;
1247 self.saturation = 1.0;
1248 self.refraction_depth = 0.0;
1249 self.refraction_depth_dp = None;
1250 self.dispersion = 0.0;
1251 self.edge_spectrum = None;
1252 self.transmission_refraction = 0.0;
1253 self.meniscus_absorption = 0.0;
1254 self.rim_reflection = 0.0;
1255 self.adaptive_frost = 0.0;
1256 self
1257 }
1258
1259 fn clips_in_shader(&self) -> bool {
1260 self.key_fill.and_then(GlassKeyFill::uniforms).is_some()
1261 }
1262 pub(crate) fn backdrop_effect(&self, density: f32, dynamics: GlassDynamics) -> RenderEffect {
1263 self.runtime_effect(density, &dynamics, false)
1264 }
1265
1266 fn blur_radii(&self, density: f32, activity: f32, content_mask: bool) -> (f32, f32) {
1267 if content_mask || self.tint_amount.is_some() {
1268 return (0.0, 0.0);
1269 }
1270 let radius = self.blur_radius_dp * density * activity;
1271 if radius > WCKSRD_OPTICAL_BLUR_RADIUS_PX {
1272 (0.0, radius)
1273 } else {
1274 (radius, 0.0)
1275 }
1276 }
1277
1278 fn runtime_effect(
1279 &self,
1280 density: f32,
1281 dynamics: &GlassDynamics,
1282 content_mask: bool,
1283 ) -> RenderEffect {
1284 self.runtime_effect_with_content(density, dynamics, content_mask, false)
1285 }
1286
1287 fn set_refraction_uniforms(
1288 &self,
1289 shader: &mut RuntimeShader,
1290 dynamics: &GlassDynamics,
1291 activity: f32,
1292 ) -> (f32, f32, f32) {
1293 let press_depth = dynamics.press_depth.unwrap_or(1.0).clamp(0.0, 1.0);
1294 shader.set_float(9, self.refraction_depth * activity * press_depth);
1295 let refraction = dynamics.refraction.unwrap_or(self.refraction);
1296 let (refraction_mode, refraction_reach) = refraction.shader_parameters();
1297 let refraction_weight = if matches!(refraction, GlassRefraction::LayeredSurface { .. }) {
1298 1.0
1299 } else {
1300 activity * press_depth
1301 };
1302 shader.set_float(GLASS_REFRACTION_MODE_UNIFORM, refraction_mode);
1303 if let GlassRefraction::LayeredSurface {
1304 return_reach_dp,
1305 return_depth_dp,
1306 ..
1307 } = refraction
1308 {
1309 shader.set_float2(
1310 GLASS_LAYERED_RETURN_UNIFORM,
1311 finite_nonnegative(return_reach_dp),
1312 finite_nonnegative(return_depth_dp),
1313 );
1314 }
1315 set_edge_return_depth(shader, dynamics.edge_return_depth_dp);
1316 shader.set_float(
1317 GLASS_EDGE_REFRACTION_REACH_UNIFORM,
1318 refraction_reach * refraction_weight,
1319 );
1320 shader.set_float(
1321 GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM,
1322 self.refraction_depth_dp.unwrap_or(0.0) * refraction_weight,
1323 );
1324 shader.set_float(
1325 GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
1326 if self.refraction_depth_dp.is_some() {
1327 1.0
1328 } else {
1329 0.0
1330 },
1331 );
1332 shader.set_float(
1333 GLASS_REFRACTION_CURVE_UNIFORM,
1334 self.refraction_curve * activity,
1335 );
1336 shader.set_float(
1337 GLASS_DISPERSION_UNIFORM,
1338 self.dispersion * activity * press_depth,
1339 );
1340 let spectrum_reach = self.set_spectrum_uniforms(shader);
1341 shader.set_float(
1342 GLASS_TRANSMISSION_REFRACTION_UNIFORM,
1343 self.transmission_refraction * if refraction_mode > 2.5 { 1.0 } else { activity },
1344 );
1345 shader.set_float(
1346 GLASS_MENISCUS_ABSORPTION_UNIFORM,
1347 self.meniscus_absorption * press_depth,
1348 );
1349 shader.set_float(
1350 GLASS_FOLD_DEPTH_UNIFORM,
1351 self.fold_depth.max(0.0) * press_depth,
1352 );
1353 (refraction_mode, refraction_reach, spectrum_reach)
1354 }
1355
1356 fn set_shadow_uniforms(
1357 &self,
1358 shader: &mut RuntimeShader,
1359 dynamics: &GlassDynamics,
1360 activity: f32,
1361 ) -> (f32, f32) {
1362 let dynamic_shadow = !self.clip && self.shadow;
1363 let shadow = dynamics
1364 .shadow
1365 .filter(|shadow| shadow.uniforms().is_some())
1366 .unwrap_or_else(|| {
1367 GlassShadow::new(
1368 self.shadow_color
1369 .with_alpha(self.shadow_color.a() * 0.55 * activity),
1370 self.shadow_radius,
1371 self.shadow_offset_y,
1372 self.shadow_spread,
1373 )
1374 });
1375 shader.set_float(
1376 102,
1377 if dynamic_shadow {
1378 shadow.color.a()
1379 } else {
1380 0.0
1381 },
1382 );
1383 shader.set_float(103, shadow.radius);
1384 shader.set_float(104, shadow.offset_y);
1385 shader.set_float(105, shadow.spread);
1386 shader.set_float4(
1387 cranpose_ui_graphics::GLASS_SHADOW_COLOR_UNIFORM,
1388 shadow.color.r(),
1389 shadow.color.g(),
1390 shadow.color.b(),
1391 0.0,
1392 );
1393 let ring_shadow = dynamics.ring_shadow.filter(|(ring, width)| {
1394 ring.color.a() > 0.0 && ring.uniforms().is_some() && width.is_finite() && *width > 0.0
1395 });
1396 if let Some((ring, width)) = ring_shadow {
1397 shader.set_float4(
1398 cranpose_ui_graphics::GLASS_RING_SHADOW_UNIFORM,
1399 ring.color.r(),
1400 ring.color.g(),
1401 ring.color.b(),
1402 ring.color.a(),
1403 );
1404 shader.set_float4(
1405 cranpose_ui_graphics::GLASS_RING_SHADOW_UNIFORM + 4,
1406 ring.radius,
1407 ring.offset_y,
1408 width,
1409 1.0,
1410 );
1411 shader.set_float(
1412 cranpose_ui_graphics::GLASS_RING_SHADOW_UNIFORM + 8,
1413 ring.spread,
1414 );
1415 }
1416 let ring_reach = ring_shadow.map_or(0.0, |(ring, width)| {
1417 3.0 * ring.radius + ring.offset_y.abs() + width * 0.5 + ring.spread.abs()
1418 });
1419 let contour_reach = self
1420 .contour_highlight
1421 .and_then(GlassContourHighlight::uniforms)
1422 .filter(|values| values[2] > 0.0 && activity > 0.0)
1423 .map_or(0.0, |values| (-values[1]).max(0.0));
1424 shader.set_output_padding(ring_reach.max(contour_reach));
1425 let visible_shadow = dynamic_shadow && shadow.color.a() > 0.0;
1426 let shadow_reach = if visible_shadow {
1427 shadow.radius + shadow.offset_y.abs() + shadow.spread.max(0.0)
1428 } else {
1429 0.0
1430 }
1431 .max(ring_reach)
1432 .max(contour_reach);
1433 let offset = if visible_shadow || ring_shadow.is_some() {
1434 shadow
1435 .offset_y
1436 .abs()
1437 .max(ring_shadow.map_or(0.0, |(ring, _)| ring.offset_y.abs()))
1438 } else {
1439 0.0
1440 };
1441 (shadow_reach, offset)
1442 }
1443
1444 fn runtime_effect_with_content(
1445 &self,
1446 density: f32,
1447 dynamics: &GlassDynamics,
1448 content_mask: bool,
1449 separate_content: bool,
1450 ) -> RenderEffect {
1451 let density = density.max(f32::EPSILON);
1452 let activity = dynamics
1453 .activity
1454 .filter(|value| value.is_finite())
1455 .unwrap_or(1.0)
1456 .clamp(0.0, 1.0);
1457 static SHADER: OnceLock<RuntimeShader> = OnceLock::new();
1458 let mut shader = SHADER
1459 .get_or_init(|| {
1460 let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
1461 shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
1462 shader.set_float2(GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM, 0.0, 0.0);
1463 specialize_liquid_glass(&mut shader);
1464 shader
1465 })
1466 .clone();
1467 if let Some(morph) = dynamics.morph.as_ref() {
1468 set_morph_uniforms(&mut shader, morph, activity);
1469 } else {
1470 shader.set_float2(0, 0.0, 0.0);
1471 shader.set_float(6, self.shape.shader_radius_px(density));
1472 }
1473 let projection = dynamics.projection();
1474 shader.set_float2(GLASS_OPTICAL_PROJECTION_UNIFORM, projection.0, projection.1);
1475 let (refraction_mode, refraction_reach, spectrum_reach) =
1476 self.set_refraction_uniforms(&mut shader, dynamics, activity);
1477 shader.set_float(
1478 GLASS_OPTICAL_ZOOM_UNIFORM,
1479 1.0 + (self.optical_zoom - 1.0).max(0.0) * activity,
1480 );
1481 let zoom_anchor = dynamics
1482 .morph
1483 .as_ref()
1484 .map_or((0.0, 0.0), |morph| morph.zoom_anchor);
1485 shader.set_float2(
1486 GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
1487 zoom_anchor.0,
1488 zoom_anchor.1,
1489 );
1490 shader.set_float(121, self.rim_reflection.max(0.001));
1491 let (ink_color, ink_strength) = self
1492 .ink_recolor
1493 .map_or((Color::TRANSPARENT, 0.0), |(color, strength)| {
1494 (color, strength * activity)
1495 });
1496 shader.set_float(124, ink_color.r());
1497 shader.set_float(125, ink_color.g());
1498 shader.set_float(126, ink_color.b());
1499 shader.set_float(127, ink_strength);
1500 let (light_x, light_y) = glass_light_direction();
1501 shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM, light_x);
1502 shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM + 1, light_y);
1503 self.set_lighting_uniforms(&mut shader, dynamics);
1504 shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, density);
1505 shader.set_float(
1506 11,
1507 (self.highlight + dynamics.highlight_boost).clamp(0.0, 2.0) * activity,
1508 );
1509 let dynamic_tint = boost_tint_saturation(self.tint, dynamics.saturation_boost);
1510 let dynamic_tint_alpha = (dynamic_tint.a()
1511 * dynamics
1512 .tint_alpha_multiplier
1513 .unwrap_or(1.0)
1514 .clamp(0.0, 2.0)
1515 * activity)
1516 .clamp(0.0, 1.0);
1517 shader.set_float4(
1518 14,
1519 dynamic_tint.r(),
1520 dynamic_tint.g(),
1521 dynamic_tint.b(),
1522 dynamic_tint_alpha,
1523 );
1524 if let Some((color, progress)) = dynamics.tint_crossfade.filter(|(_, p)| p.is_finite()) {
1525 let index = cranpose_ui_graphics::liquid_glass::GLASS_TINT_CROSSFADE_UNIFORM;
1526 shader.set_float(index, 1.0 + progress.clamp(0.0, 1.0));
1527 shader.set_float4(index + 1, color.r(), color.g(), color.b(), color.a());
1528 }
1529 let saturation = (self.saturation + dynamics.saturation_boost).max(0.0);
1530 shader.set_float(18, 1.0 + (saturation - 1.0) * activity);
1531 shader.set_float(20, self.lift * activity);
1532 shader.set_float(21, 0.5 * activity);
1533 shader.set_float2(22, 0.0, 1.0);
1534 shader.set_float(24, 1.0 + (self.contrast - 1.0) * activity);
1535 shader.set_float(28, self.rim_style * activity);
1536 let (wcksrd_blur_radius, gaussian_blur_radius) =
1537 self.blur_radii(density, activity, content_mask);
1538 shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, wcksrd_blur_radius);
1539 shader.set_float(GLASS_ACTIVITY_UNIFORM, activity);
1540 shader.set_float(
1541 GLASS_RESTING_EDGE_SHARPNESS_UNIFORM,
1542 self.resting_edge_sharpness,
1543 );
1544 let resting_tint = dynamics.resting_tint.unwrap_or(Color::TRANSPARENT);
1545 shader.set_float4(
1546 GLASS_RESTING_TINT_UNIFORM,
1547 resting_tint.r(),
1548 resting_tint.g(),
1549 resting_tint.b(),
1550 resting_tint.a(),
1551 );
1552 shader.set_float(112, if content_mask { 1.0 } else { 0.0 });
1553 shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, self.adaptive_frost * activity);
1554 shader.set_float(97, self.foreground_luma);
1555 shader.set_float(GLASS_ADAPTIVE_TONE_UNIFORM, f32::from(self.adaptive_tone));
1556 self.set_backdrop_uniforms(&mut shader, density, content_mask);
1557 shader.set_float(
1558 cranpose_ui_graphics::GLASS_FOREGROUND_CONTENT_UNIFORM,
1559 f32::from(separate_content),
1560 );
1561 let (shadow_reach, shadow_offset) = if content_mask {
1562 (0.0, 0.0)
1563 } else {
1564 self.set_shadow_uniforms(&mut shader, dynamics, activity)
1565 };
1566 let morph_pad = dynamics.morph.as_ref().map_or(0.0, |morph| {
1567 let (px, py, pw, ph, _) = morph.primary;
1568 let (left, top) = (px - pw * 0.5, py - ph * 0.5);
1569 let (right, bottom) = (px + pw * 0.5, py + ph * 0.5);
1570 let mut shape_reach = 0.0f32;
1571 for (sx, sy, sw, sh, _) in &morph.shapes {
1572 let reach_x = ((sx + sw * 0.5) - right)
1573 .max(left - (sx - sw * 0.5))
1574 .max(0.0);
1575 let reach_y = ((sy + sh * 0.5) - bottom)
1576 .max(top - (sy - sh * 0.5))
1577 .max(0.0);
1578 shape_reach = shape_reach.max(reach_x.max(reach_y));
1579 }
1580 let glue_pad = if morph.shapes.is_empty() {
1581 0.0
1582 } else {
1583 morph.glue * 2.0
1584 };
1585 morph.wobble_amplitude * 2.0 + morph.bulge_amplitude + shape_reach + glue_pad
1586 });
1587 shader.set_input_padding(if content_mask {
1588 0.0
1589 } else {
1590 (self.input_padding()
1591 + if refraction_mode > 2.5 {
1592 refraction_reach + spectrum_reach
1593 } else if refraction_mode >= 1.5 {
1594 refraction_reach * (26.5 / 9.0) + spectrum_reach
1595 } else {
1596 refraction_reach
1597 }
1598 + morph_pad
1599 + wcksrd_blur_radius / density)
1600 * projection.0.max(projection.1).max(1.0)
1601 });
1602 if let Some(morph) = dynamics.morph.as_ref() {
1603 let support =
1604 morph_output_support(morph, activity, morph_pad, shadow_reach, shadow_offset);
1605 let (cx, cy, _, _, _) = morph.primary;
1606 let support = Rect {
1607 x: cx + (support.x - cx) * projection.0,
1608 y: cy + (support.y - cy) * projection.1,
1609 width: support.width * projection.0,
1610 height: support.height * projection.1,
1611 };
1612 let (node_w, node_h) = morph.node_size;
1613 let overhang = (-support.x)
1614 .max(-support.y)
1615 .max(support.x + support.width - node_w)
1616 .max(support.y + support.height - node_h)
1617 .max(0.0);
1618 shader.set_output_padding((morph_pad + shadow_reach + 4.0).max(overhang));
1619 shader.set_output_support(Some(support));
1620 }
1621
1622 let optical_effect = liquid_glass_runtime_effect(shader);
1623 if gaussian_blur_radius > f32::EPSILON {
1624 RenderEffect::blur_with_edge_treatment(gaussian_blur_radius, TileMode::Mirror)
1625 .then(optical_effect)
1626 } else {
1627 optical_effect
1628 }
1629 }
1630
1631 fn set_backdrop_uniforms(&self, shader: &mut RuntimeShader, density: f32, content_mask: bool) {
1632 if let Some(values) = self.face_tone.and_then(GlassFaceTone::uniforms) {
1633 for (index, value) in values.into_iter().enumerate() {
1634 shader.set_float(cranpose_ui_graphics::GLASS_FACE_TONE_UNIFORM + index, value);
1635 }
1636 }
1637 if let Some(amount) = self.tint_amount {
1638 shader.set_float(
1639 cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM,
1640 self.pane_blur_radius * density,
1641 );
1642 shader.set_float(
1643 cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM + 1,
1644 amount.percent() / 100.0,
1645 );
1646 }
1647 if !content_mask && let Some((radius, opacity)) = self.backdrop_blur {
1648 shader.set_float2(
1649 cranpose_ui_graphics::GLASS_BACKDROP_BLUR_UNIFORM,
1650 radius * density,
1651 opacity,
1652 );
1653 }
1654 }
1655
1656 fn set_spectrum_uniforms(&self, shader: &mut RuntimeShader) -> f32 {
1657 let spectrum = self
1658 .edge_spectrum
1659 .and_then(|spectrum| spectrum.uniforms().map(|values| (spectrum, values)));
1660 if let Some((spectrum, values)) = spectrum {
1661 for (index, value) in values.into_iter().enumerate() {
1662 shader.set_float(GLASS_EDGE_SPECTRUM_UNIFORM + index, value);
1663 }
1664 spectrum.capture_reach()
1665 } else {
1666 40.787 * self.dispersion
1667 }
1668 }
1669
1670 fn set_lighting_uniforms(&self, shader: &mut RuntimeShader, dynamics: &GlassDynamics) {
1671 if let Some(values) = self
1672 .specular_highlight
1673 .and_then(GlassSpecularHighlight::uniforms)
1674 {
1675 for (index, value) in values.into_iter().enumerate() {
1676 shader.set_float(
1677 cranpose_ui_graphics::GLASS_SPECULAR_HIGHLIGHT_UNIFORM + index,
1678 value,
1679 );
1680 }
1681 }
1682 shader.set_float(
1683 cranpose_ui_graphics::GLASS_CONTACT_LIGHT_UNIFORM,
1684 f32::from(dynamics.contact_lighting),
1685 );
1686 if let Some(black) = self.tint_transmission.filter(|value| value.is_finite()) {
1687 shader.set_float2(
1688 cranpose_ui_graphics::GLASS_TINT_TRANSMISSION_UNIFORM,
1689 black.clamp(0.0, 1.0),
1690 1.0,
1691 );
1692 }
1693 let (touch_x, touch_y, touch_intensity) = dynamics.touch.unwrap_or((0.0, 0.0, 0.0));
1694 shader.set_float(118, touch_x);
1695 shader.set_float(119, touch_y);
1696 shader.set_float(120, touch_intensity.clamp(0.0, 1.0));
1697 shader.set_float(
1698 GLASS_FACE_LIGHTING_OFF_UNIFORM,
1699 f32::from(!self.face_lighting),
1700 );
1701 shader.set_float(
1702 GLASS_TOUCH_RADIUS_UNIFORM,
1703 dynamics
1704 .touch_radius_dp
1705 .filter(|radius| radius.is_finite() && *radius > 0.0)
1706 .unwrap_or(58.0),
1707 );
1708 shader.set_float(
1709 GLASS_LAYER_CLIPPED_UNIFORM,
1710 f32::from(self.clip && !self.clips_in_shader()),
1711 );
1712 if let Some(values) = self.key_fill.and_then(GlassKeyFill::uniforms) {
1713 for (index, value) in values.into_iter().enumerate() {
1714 shader.set_float(GLASS_KEY_FILL_UNIFORM + index, value);
1715 }
1716 }
1717 if let Some(values) = self.face_response.and_then(GlassFaceResponse::uniforms) {
1718 for (index, value) in values.into_iter().enumerate() {
1719 shader.set_float(GLASS_FACE_RESPONSE_UNIFORM + index, value);
1720 }
1721 }
1722 if let Some(values) = self
1723 .contour_highlight
1724 .and_then(GlassContourHighlight::uniforms)
1725 {
1726 for (index, value) in values.into_iter().enumerate() {
1727 shader.set_float(
1728 cranpose_ui_graphics::GLASS_CONTOUR_HIGHLIGHT_UNIFORM + index,
1729 value,
1730 );
1731 }
1732 }
1733 if let Some(values) = self.inner_shadow.and_then(GlassShadow::uniforms) {
1734 for (index, value) in values.into_iter().enumerate() {
1735 shader.set_float(GLASS_INNER_SHADOW_UNIFORM + index, value);
1736 }
1737 }
1738 }
1739
1740 fn input_padding(&self) -> f32 {
1741 2.0
1742 }
1743}
1744
1745pub trait LiquidModifierExt {
1747 fn glass_effect(self, glass: Glass) -> Modifier;
1753
1754 fn glass_effect_with(
1758 self,
1759 glass: Glass,
1760 dynamics: impl Fn() -> GlassDynamics + 'static,
1761 ) -> Modifier;
1762}
1763
1764impl LiquidModifierExt for Modifier {
1765 fn glass_effect(self, glass: Glass) -> Modifier {
1766 self.glass_effect_with(glass, GlassDynamics::default)
1767 }
1768
1769 fn glass_effect_with(
1770 self,
1771 glass: Glass,
1772 dynamics: impl Fn() -> GlassDynamics + 'static,
1773 ) -> Modifier {
1774 let colors = crate::theme::liquid_colors();
1775 let options = cranpose_services::local_accessibility_options().current();
1776 let resolved = if options.reduce_transparency {
1777 glass.resolve(&colors).without_transparency(&colors)
1778 } else {
1779 glass.resolve(&colors)
1780 };
1781 let shape = resolved.shape;
1782
1783 let mut modifier = self;
1784 if resolved.shadow && resolved.clip {
1785 let shadow_color = resolved.shadow_color;
1786 let (radius, offset_y, spread) = (
1787 resolved.shadow_radius,
1788 resolved.shadow_offset_y,
1789 resolved.shadow_spread,
1790 );
1791 modifier = modifier.drop_shadow(shape.layer_shape(), move |scope| {
1792 scope.radius = radius;
1793 scope.spread = spread;
1794 scope.offset.y = offset_y;
1795 scope.color = shadow_color;
1796 scope.cutout = true;
1797 });
1798 }
1799
1800 let layer = cached_glass_layer(resolved);
1801 modifier.graphics_layer(move || layer(current_density(), dynamics()))
1802 }
1803}
1804
1805struct CachedGlassLayer<T> {
1806 density: u32,
1807 light: [u32; 2],
1808 dynamics: GlassDynamics,
1809 value: T,
1810}
1811
1812fn cached_glass_layer(resolved: ResolvedGlass) -> impl Fn(f32, GlassDynamics) -> GraphicsLayer {
1813 cached_glass_layers(resolved, false, std::convert::identity)
1814}
1815
1816pub(crate) fn cached_glass_content_layers(
1817 resolved: ResolvedGlass,
1818) -> impl Fn(f32, GlassDynamics) -> [GraphicsLayer; 2] {
1819 cached_glass_layers(resolved, true, |mut layer| {
1820 let Some(RenderEffect::Chain { first, second }) = layer.backdrop_effect.take() else {
1821 panic!("content layers require a sequential edge lens");
1822 };
1823 layer.backdrop_effect = Some((*second).clone());
1824 [
1825 GraphicsLayer {
1826 backdrop_effect: Some((*first).clone()),
1827 ..Default::default()
1828 },
1829 layer,
1830 ]
1831 })
1832}
1833
1834fn cached_glass_layers<T: Clone>(
1835 resolved: ResolvedGlass,
1836 separate_content: bool,
1837 assemble: impl Fn(GraphicsLayer) -> T,
1838) -> impl Fn(f32, GlassDynamics) -> T {
1839 let cached = RefCell::new(None::<CachedGlassLayer<T>>);
1840 move |density, dynamics| {
1841 let direction = glass_light_direction();
1842 let light = [direction.0, direction.1].map(f32::to_bits);
1843 let mut cached = cached.borrow_mut();
1844 if let Some(previous) = &*cached
1845 && previous.density == density.to_bits()
1846 && previous.light == light
1847 && glass_dynamics_match(&previous.dynamics, &dynamics)
1848 {
1849 return previous.value.clone();
1850 }
1851 let render_effect = ((!resolved.clip && dynamics.morph.is_some())
1852 || (resolved.clip && resolved.clips_in_shader()))
1853 .then(|| resolved.runtime_effect(density, &dynamics, true));
1854 let layer = GraphicsLayer {
1855 backdrop_effect: Some(resolved.runtime_effect_with_content(
1856 density,
1857 &dynamics,
1858 false,
1859 separate_content,
1860 )),
1861 render_effect,
1862 shape: resolved.shape.layer_shape(),
1863 clip: resolved.clip && !resolved.clips_in_shader(),
1864 ..Default::default()
1865 };
1866 let value = assemble(layer);
1867 *cached = Some(CachedGlassLayer {
1868 density: density.to_bits(),
1869 light,
1870 dynamics,
1871 value: value.clone(),
1872 });
1873 value
1874 }
1875}
1876
1877fn glass_dynamics_match(a: &GlassDynamics, b: &GlassDynamics) -> bool {
1878 let bits = |d: &GlassDynamics| {
1879 let GlassDynamics {
1880 contact_lighting,
1881 refraction,
1882 shadow,
1883 ring_shadow,
1884 edge_return_depth_dp,
1885 optical_projection,
1886 activity,
1887 resting_tint,
1888 highlight_boost,
1889 saturation_boost,
1890 tint_alpha_multiplier,
1891 tint_crossfade,
1892 morph: _,
1893 touch,
1894 touch_radius_dp,
1895 press_depth,
1896 } = d;
1897 (
1898 (
1899 *contact_lighting,
1900 ring_shadow.map(|(ring, width)| (shadow_bits(ring), width.to_bits())),
1901 tint_crossfade.map(|(Color(r, g, b, a), p)| [r, g, b, a, p].map(f32::to_bits)),
1902 ),
1903 refraction.map(|refraction| {
1904 match refraction {
1905 GlassRefraction::Radial => [0.0, 0.0, 0.0, 0.0],
1906 GlassRefraction::Surface { reach_dp } => [1.0, reach_dp, 0.0, 0.0],
1907 GlassRefraction::EdgeLens { reach_dp } => [2.0, reach_dp, 0.0, 0.0],
1908 GlassRefraction::LayeredSurface {
1909 reach_dp,
1910 return_reach_dp,
1911 return_depth_dp,
1912 } => [3.0, reach_dp, return_reach_dp, return_depth_dp],
1913 }
1914 .map(f32::to_bits)
1915 }),
1916 shadow.map(shadow_bits),
1917 edge_return_depth_dp.map(f32::to_bits),
1918 optical_projection.map(|(x, y)| [x, y].map(f32::to_bits)),
1919 activity.map(f32::to_bits),
1920 resting_tint.map(|Color(r, g, b, a)| [r, g, b, a].map(f32::to_bits)),
1921 [*highlight_boost, *saturation_boost].map(f32::to_bits),
1922 tint_alpha_multiplier.map(f32::to_bits),
1923 touch.map(|(x, y, strength)| [x, y, strength].map(f32::to_bits)),
1924 press_depth.map(f32::to_bits),
1925 touch_radius_dp.map(f32::to_bits),
1926 )
1927 };
1928 bits(a) == bits(b)
1929 && match (&a.morph, &b.morph) {
1930 (None, None) => true,
1931 (Some(a), Some(b)) => glass_morphs_match(a, b),
1932 _ => false,
1933 }
1934}
1935
1936fn shadow_bits(shadow: GlassShadow) -> [u32; 7] {
1937 [
1938 shadow.color.r(),
1939 shadow.color.g(),
1940 shadow.color.b(),
1941 shadow.color.a(),
1942 shadow.radius,
1943 shadow.offset_y,
1944 shadow.spread,
1945 ]
1946 .map(f32::to_bits)
1947}
1948
1949fn glass_morphs_match(a: &GlassMorph, b: &GlassMorph) -> bool {
1950 let shape_bits =
1951 |&(x, y, w, h, r): &(f32, f32, f32, f32, f32)| [x, y, w, h, r].map(f32::to_bits);
1952 let bits = |m: &GlassMorph| {
1953 let GlassMorph {
1954 node_size,
1955 primary,
1956 shapes: _,
1957 glue,
1958 wobble_amplitude,
1959 wobble_phase,
1960 bulge_amplitude,
1961 bulge_direction,
1962 ellipse_blend,
1963 capsule_smoothing_dp,
1964 deformation,
1965 zoom_anchor,
1966 } = m;
1967 (
1968 [
1969 node_size.0,
1970 node_size.1,
1971 *glue,
1972 *wobble_amplitude,
1973 *wobble_phase,
1974 *bulge_amplitude,
1975 *bulge_direction,
1976 *ellipse_blend,
1977 *capsule_smoothing_dp,
1978 zoom_anchor.0,
1979 zoom_anchor.1,
1980 ]
1981 .map(f32::to_bits),
1982 shape_bits(primary),
1983 deformation
1984 .map(|GlassDeformation { axis, along }| [axis.0, axis.1, along].map(f32::to_bits)),
1985 )
1986 };
1987 bits(a) == bits(b)
1988 && a.shapes
1989 .iter()
1990 .map(shape_bits)
1991 .eq(b.shapes.iter().map(shape_bits))
1992}
1993
1994fn morph_output_support(
1995 morph: &GlassMorph,
1996 activity: f32,
1997 morph_pad: f32,
1998 shadow_reach: f32,
1999 shadow_offset: f32,
2000) -> Rect {
2001 let (px, py, pw, ph, _) = morph.primary;
2002 let (along, (ax, ay)) = morph.deformation.map_or((1.0, (1.0, 0.0)), |deformation| {
2003 (
2004 1.0 + (deformation.along() - 1.0) * activity,
2005 deformation.axis(),
2006 )
2007 });
2008 let across = 1.0 / along;
2009 let smallest_strain = along.min(across);
2010 let reach = (morph_pad + shadow_reach + 4.0) / smallest_strain;
2011 let rows = [
2012 (
2013 (along * ax * ax + across * ay * ay).abs(),
2014 ((along - across) * ax * ay).abs(),
2015 ),
2016 (
2017 ((along - across) * ax * ay).abs(),
2018 (along * ay * ay + across * ax * ax).abs(),
2019 ),
2020 ];
2021 let half = (pw * 0.5 + reach, ph * 0.5 + reach);
2022 let extent_x = rows[0].0 * half.0 + rows[0].1 * half.1;
2023 let extent_y = rows[1].0 * half.0 + rows[1].1 * half.1;
2024 let (mut left, mut top) = (px - extent_x, py - extent_y);
2025 let (mut right, mut bottom) = (px + extent_x, py + extent_y);
2026 for (sx, sy, sw, sh, _) in &morph.shapes {
2027 left = left.min(sx - sw * 0.5 - reach);
2028 top = top.min(sy - sh * 0.5 - reach);
2029 right = right.max(sx + sw * 0.5 + reach);
2030 bottom = bottom.max(sy + sh * 0.5 + reach);
2031 }
2032 Rect {
2033 x: left,
2034 y: top - shadow_offset,
2035 width: right - left,
2036 height: bottom - top + 2.0 * shadow_offset,
2037 }
2038}
2039
2040fn set_morph_uniforms(shader: &mut RuntimeShader, morph: &GlassMorph, activity: f32) {
2041 let (node_w, node_h) = morph.node_size;
2042 let (cx, cy, w, h, radius) = morph.primary;
2043 shader.set_float2(0, node_w.max(1.0), node_h.max(1.0));
2044 shader.set_float2(2, cx, cy);
2045 shader.set_float2(4, w, h);
2046 shader.set_float(6, radius);
2047 let count = morph.shapes.len().min(GlassMorph::MAX_SHAPES);
2048 shader.set_float(30, count as f32);
2049 for (index, (sx, sy, sw, sh, sr)) in morph.shapes.iter().take(count).enumerate() {
2050 let base = 36 + index * 5;
2051 shader.set_float(base, *sx);
2052 shader.set_float(base + 1, *sy);
2053 shader.set_float(base + 2, *sw);
2054 shader.set_float(base + 3, *sh);
2055 shader.set_float(base + 4, *sr);
2056 }
2057 shader.set_float(31, morph.glue);
2058 shader.set_float(32, morph.wobble_amplitude * activity);
2059 shader.set_float(33, morph.wobble_phase);
2060 shader.set_float(26, morph.bulge_amplitude * activity);
2061 shader.set_float(27, morph.bulge_direction);
2062 shader.set_float(110, morph.ellipse_blend.clamp(0.0, 1.0) * activity);
2063 shader.set_float(
2064 cranpose_ui_graphics::liquid_glass::GLASS_CAPSULE_SMOOTHING_UNIFORM,
2065 if morph.capsule_smoothing_dp.is_finite() {
2066 morph.capsule_smoothing_dp.max(0.0) * activity
2067 } else {
2068 0.0
2069 },
2070 );
2071 if let Some(deformation) = morph.deformation {
2072 let axis = deformation.axis();
2073 let along = 1.0 + (deformation.along() - 1.0) * activity;
2074 shader.set_float2(106, axis.0, axis.1);
2075 shader.set_float(108, along);
2076 shader.set_float(109, 1.0 / along);
2077 } else {
2078 shader.set_float2(106, 1.0, 0.0);
2079 shader.set_float2(108, 1.0, 1.0);
2080 }
2081}
2082
2083#[cfg(test)]
2084#[path = "tests/material_tests.rs"]
2085mod tests;
2086
2087#[cfg(test)]
2088#[path = "tests/material_transparency_tests.rs"]
2089mod transparency_tests;