1use std::{cell::RefCell, sync::OnceLock};
7
8use cranpose_ui::{Modifier, current_density};
9use cranpose_ui_graphics::{
10 Color, GLASS_ACTIVITY_UNIFORM, GLASS_ADAPTIVE_FROST_UNIFORM, GLASS_ADAPTIVE_TONE_UNIFORM,
11 GLASS_BLUR_RADIUS_UNIFORM, GLASS_DISPERSION_UNIFORM, GLASS_EDGE_REFRACTION_REACH_UNIFORM,
12 GLASS_EDGE_RETURN_DEPTH_UNIFORM, GLASS_EDGE_SPECTRUM_UNIFORM, GLASS_EFFECT_DENSITY_UNIFORM,
13 GLASS_FACE_LIGHTING_OFF_UNIFORM, GLASS_FACE_RESPONSE_UNIFORM, GLASS_FOLD_DEPTH_UNIFORM,
14 GLASS_INNER_SHADOW_UNIFORM, GLASS_KEY_FILL_UNIFORM, GLASS_LAYER_CLIPPED_UNIFORM,
15 GLASS_LAYERED_RETURN_UNIFORM, GLASS_LIGHT_DIRECTION_UNIFORM, GLASS_MENISCUS_ABSORPTION_UNIFORM,
16 GLASS_OPTICAL_PROJECTION_UNIFORM, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
17 GLASS_OPTICAL_ZOOM_UNIFORM, GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
18 GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM, GLASS_REFRACTION_CURVE_UNIFORM,
19 GLASS_REFRACTION_MODE_UNIFORM, GLASS_RESTING_EDGE_SHARPNESS_UNIFORM,
20 GLASS_RESTING_TINT_UNIFORM, GLASS_TOUCH_RADIUS_UNIFORM, GLASS_TRANSMISSION_REFRACTION_UNIFORM,
21 GraphicsLayer, LIQUID_GLASS_WGSL, LayerShape, PlaceholderShape, Rect, RenderEffect,
22 RoundedCornerShape, RuntimeShader, TileMode, liquid_glass_placeholder,
23 liquid_glass_runtime_effect, specialize_liquid_glass,
24};
25
26use crate::{appearance::GlassTintAmount, theme::LiquidColors};
27
28pub fn set_glass_light_direction(direction: (f32, f32)) {
31 crate::thread_state::with(|state| state.glass_light_return.set(direction));
32}
33
34pub fn glass_light_direction() -> (f32, f32) {
36 crate::thread_state::with(|state| state.glass_light_return.get())
37}
38
39const CAPSULE_CLIP_RADIUS: f32 = 1.0e6;
40
41const CAPSULE_SHADER_RADIUS: f32 = -1.0;
42
43const WCKSRD_OPTICAL_BLUR_RADIUS_PX: f32 = 2.0;
44
45#[derive(Clone, Copy, Debug, PartialEq, Default)]
47pub enum LiquidShape {
48 #[default]
50 Capsule,
51 RoundedRect(f32),
53 Circle,
55}
56
57impl LiquidShape {
58 pub fn clip_shape(&self) -> RoundedCornerShape {
60 match self {
61 LiquidShape::Capsule | LiquidShape::Circle => {
62 RoundedCornerShape::uniform(CAPSULE_CLIP_RADIUS)
63 }
64 LiquidShape::RoundedRect(radius) => RoundedCornerShape::uniform(*radius),
65 }
66 }
67
68 pub fn layer_shape(&self) -> LayerShape {
70 LayerShape::Rounded(self.clip_shape())
71 }
72
73 fn shader_radius_px(&self, density: f32) -> f32 {
74 match self {
75 LiquidShape::Capsule | LiquidShape::Circle => CAPSULE_SHADER_RADIUS,
76 LiquidShape::RoundedRect(radius) => radius * density,
77 }
78 }
79
80 fn radius_dp(&self) -> f32 {
82 match self {
83 LiquidShape::Capsule | LiquidShape::Circle => f32::MAX,
84 LiquidShape::RoundedRect(radius) => *radius,
85 }
86 }
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
92pub enum GlassVariant {
93 #[default]
95 Regular,
96 Clear,
98 Lens,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Default)]
106pub enum GlassRefraction {
107 #[default]
109 Radial,
110 Surface {
112 reach_dp: f32,
114 },
115 EdgeLens {
119 reach_dp: f32,
121 },
122 LayeredSurface {
126 reach_dp: f32,
128 return_reach_dp: f32,
130 return_depth_dp: f32,
132 },
133}
134
135impl GlassRefraction {
136 fn shader_parameters(self) -> (f32, f32) {
137 match self {
138 Self::Radial => (0.0, 0.0),
139 Self::Surface { reach_dp } => (1.0, finite_nonnegative(reach_dp)),
140 Self::EdgeLens { reach_dp } => (2.0, finite_nonnegative(reach_dp)),
141 Self::LayeredSurface { reach_dp, .. } => (3.0, finite_nonnegative(reach_dp)),
142 }
143 }
144}
145
146fn finite_nonnegative(reach_dp: f32) -> f32 {
147 if reach_dp.is_finite() {
148 reach_dp.max(0.0)
149 } else {
150 0.0
151 }
152}
153
154#[derive(Clone, Copy, Debug, PartialEq)]
158pub struct GlassShadow {
159 pub color: Color,
161 pub radius: f32,
163 pub offset_y: f32,
165 pub spread: f32,
167}
168
169impl GlassShadow {
170 pub fn new(color: Color, radius: f32, offset_y: f32, spread: f32) -> Self {
172 Self {
173 color,
174 radius: radius.max(0.0),
175 offset_y,
176 spread,
177 }
178 }
179
180 fn uniforms(self) -> Option<[f32; 8]> {
181 let values = [
182 self.color.r(),
183 self.color.g(),
184 self.color.b(),
185 self.color.a(),
186 self.radius,
187 self.offset_y,
188 self.spread,
189 1.0,
190 ];
191 (values.iter().all(|value| value.is_finite())
192 && self.radius >= 0.0
193 && (0.0..=1.0).contains(&self.color.a()))
194 .then_some(values)
195 }
196}
197
198#[derive(Clone, Debug, PartialEq, Default)]
201pub struct GlassDynamics {
202 pub contact_lighting: bool,
204 pub refraction: Option<GlassRefraction>,
207 pub shadow: Option<GlassShadow>,
209 pub ring_shadow: Option<(GlassShadow, f32)>,
213 pub edge_return_depth_dp: Option<f32>,
216 pub optical_projection: Option<(f32, f32)>,
220 pub activity: Option<f32>,
224 pub resting_tint: Option<Color>,
227 pub highlight_boost: f32,
229 pub saturation_boost: f32,
232 pub tint_alpha_multiplier: Option<f32>,
235 pub tint_crossfade: Option<(Color, f32)>,
239 pub morph: Option<GlassMorph>,
242 pub touch: Option<(f32, f32, f32)>,
246 pub touch_radius_dp: Option<f32>,
248 pub press_depth: Option<f32>,
254}
255
256impl GlassDynamics {
257 fn projection(&self) -> (f32, f32) {
258 self.optical_projection
259 .filter(|(x, y)| x.is_finite() && y.is_finite() && *x > 0.0 && *y > 0.0)
260 .unwrap_or((1.0, 1.0))
261 }
262
263 pub fn touched_up(
270 mut self,
271 press: f32,
272 finger: Option<(f32, f32)>,
273 center: (f32, f32),
274 ) -> Self {
275 let press = press.clamp(0.0, 1.0);
276 if press <= 0.0 {
277 return self;
278 }
279 self.highlight_boost += 0.85 * press;
280 self.saturation_boost += 0.45 * press;
281 let (fx, fy) = finger.unwrap_or(center);
282 let carried = self.touch.map_or(0.0, |(_, _, i)| i);
283 self.touch = Some((fx, fy, (carried + press).clamp(0.0, 1.0)));
284 self
285 }
286}
287
288fn foreground_is_dark(foreground: Color) -> bool {
289 let foreground_luma =
290 0.2126 * foreground.r() + 0.7152 * foreground.g() + 0.0722 * foreground.b();
291 foreground_luma < 0.5
292}
293
294fn shown_tint(tint: Color, crossfade: Option<(Color, f32)>) -> Color {
297 let Some((previous, progress)) = crossfade.filter(|(_, progress)| progress.is_finite()) else {
298 return tint;
299 };
300 let progress = progress.clamp(0.0, 1.0);
301 let mix = |from: f32, to: f32| from + (to - from) * progress;
302 Color(
303 mix(previous.0, tint.0),
304 mix(previous.1, tint.1),
305 mix(previous.2, tint.2),
306 mix(previous.3, tint.3),
307 )
308}
309
310fn boost_tint_saturation(tint: Color, boost: f32) -> Color {
311 if boost.abs() <= f32::EPSILON {
312 return tint;
313 }
314 let luma = 0.2126 * tint.r() + 0.7152 * tint.g() + 0.0722 * tint.b();
315 let saturation = (1.0 + boost).max(0.0);
316 Color::rgba(
317 (luma + (tint.r() - luma) * saturation).clamp(0.0, 1.0),
318 (luma + (tint.g() - luma) * saturation).clamp(0.0, 1.0),
319 (luma + (tint.b() - luma) * saturation).clamp(0.0, 1.0),
320 tint.a(),
321 )
322}
323
324pub(crate) fn neutral_surface_tint(foreground: Color, light_alpha: f32, dark_alpha: f32) -> Color {
325 if foreground_is_dark(foreground) {
326 Color::BLACK.with_alpha(light_alpha.clamp(0.0, 1.0))
327 } else {
328 Color::WHITE.with_alpha(dark_alpha.clamp(0.0, 1.0))
329 }
330}
331
332#[derive(Clone, Debug, PartialEq, Default)]
343pub struct GlassMorph {
344 pub node_size: (f32, f32),
350 pub primary: (f32, f32, f32, f32, f32),
353 pub shapes: Vec<(f32, f32, f32, f32, f32)>,
355 pub glue: f32,
357 pub wobble_amplitude: f32,
359 pub wobble_phase: f32,
361 pub bulge_amplitude: f32,
365 pub bulge_direction: f32,
368 pub ellipse_blend: f32,
372 pub capsule_smoothing_dp: f32,
377 pub deformation: Option<GlassDeformation>,
381 pub zoom_anchor: (f32, f32),
387}
388
389#[derive(Clone, Copy, Debug, PartialEq)]
393pub struct GlassDeformation {
394 axis: (f32, f32),
395 along: f32,
396}
397
398impl GlassDeformation {
399 pub fn incompressible(axis: (f32, f32), along: f32) -> Self {
403 let length = (axis.0 * axis.0 + axis.1 * axis.1).sqrt();
404 let axis = if length > f32::EPSILON {
405 (axis.0 / length, axis.1 / length)
406 } else {
407 (1.0, 0.0)
408 };
409 Self {
410 axis,
411 along: along.max(f32::EPSILON),
412 }
413 }
414
415 pub fn axis(self) -> (f32, f32) {
417 self.axis
418 }
419
420 pub fn along(self) -> f32 {
422 self.along
423 }
424
425 pub fn across(self) -> f32 {
427 1.0 / self.along
428 }
429}
430
431impl GlassMorph {
432 pub const MAX_SHAPES: usize = 8;
434}
435
436#[derive(Clone, Copy, Debug, PartialEq)]
440pub struct GlassContourHighlight {
441 pub height_dp: f32,
443 pub offset_dp: f32,
445 pub amount: f32,
447 pub color_bias: f32,
449 pub angle_radians: f32,
451 pub spread_radians: f32,
453}
454
455impl GlassContourHighlight {
456 fn uniforms(self) -> Option<[f32; 6]> {
457 let values = [
458 self.height_dp,
459 self.offset_dp,
460 self.amount,
461 self.color_bias,
462 self.angle_radians,
463 self.spread_radians,
464 ];
465 (values.iter().all(|value| value.is_finite())
466 && self.height_dp > 0.0
467 && self.spread_radians > 0.0)
468 .then_some(values)
469 }
470}
471
472#[derive(Clone, Copy, Debug, PartialEq)]
475pub struct GlassSpecularHighlight {
476 pub height_dp: f32,
478 pub angle_radians: f32,
480 pub spread_radians: f32,
482 pub curvature: f32,
484 pub diffuse_amount: f32,
486 pub diffuse_height_dp: f32,
488 pub diffuse_spread_radians: f32,
490 pub saturation: f32,
492 pub luma_gain: f32,
494 pub color_bias: f32,
496}
497
498impl GlassSpecularHighlight {
499 fn uniforms(self) -> Option<[f32; 10]> {
500 let values = [
501 self.height_dp,
502 self.angle_radians,
503 self.spread_radians,
504 self.curvature,
505 self.diffuse_amount,
506 self.diffuse_height_dp,
507 self.diffuse_spread_radians,
508 self.saturation,
509 self.luma_gain,
510 self.color_bias,
511 ];
512 (values.iter().all(|value| value.is_finite())
513 && self.height_dp > 0.0
514 && self.spread_radians > 0.0
515 && self.curvature > 0.0
516 && self.diffuse_height_dp > 0.0
517 && self.diffuse_spread_radians > 0.0)
518 .then_some(values)
519 }
520}
521
522#[derive(Clone, Copy, Debug, PartialEq)]
525pub struct GlassKeyFill {
526 pub height_dp: f32,
528 pub curvature: f32,
530 pub angle_radians: f32,
532 pub saturation: f32,
534 pub luma_gain: f32,
536 pub offset: f32,
538 pub scale_with_surface: bool,
540}
541
542impl GlassKeyFill {
543 fn uniforms(self) -> Option<[f32; 7]> {
544 let values = [
545 self.height_dp,
546 self.curvature,
547 self.angle_radians,
548 self.saturation,
549 self.luma_gain,
550 self.offset,
551 f32::from(self.scale_with_surface),
552 ];
553 (values.iter().all(|value| value.is_finite())
554 && self.height_dp > 0.0
555 && self.curvature > 0.0)
556 .then_some(values)
557 }
558}
559
560#[derive(Clone, Copy, Debug, PartialEq)]
563pub struct GlassSpectrum {
564 pub angle_radians: f32,
567 pub step_dp: f32,
569 pub vertical_scale: f32,
571 pub opacity_near: f32,
573 pub opacity_far: f32,
575 pub fade_depth_dp: f32,
577 pub extent_dp: f32,
579}
580
581impl GlassSpectrum {
582 fn uniforms(self) -> Option<[f32; 8]> {
583 let values = [
584 self.angle_radians,
585 self.step_dp,
586 self.vertical_scale,
587 self.opacity_near,
588 self.opacity_far,
589 self.fade_depth_dp,
590 self.extent_dp,
591 1.0,
592 ];
593 (values.iter().all(|value| value.is_finite())
594 && self.vertical_scale >= 0.0
595 && (0.0..=1.0).contains(&self.opacity_near)
596 && (0.0..=1.0).contains(&self.opacity_far)
597 && self.fade_depth_dp >= 0.0
598 && self.extent_dp >= 0.0)
599 .then_some(values)
600 }
601
602 fn capture_reach(self) -> f32 {
603 self.step_dp.abs() * 3.0 * self.vertical_scale.max(1.0)
604 }
605}
606
607#[derive(Clone, Copy, Debug, PartialEq)]
611pub struct GlassFaceResponse {
612 pub gain: f32,
614 pub start_dp: f32,
616 pub end_dp: f32,
619 pub illumination: f32,
621}
622
623impl GlassFaceResponse {
624 fn uniforms(self) -> Option<[f32; 4]> {
625 let values = [
626 1.0 - self.gain,
627 self.start_dp,
628 self.end_dp,
629 self.illumination,
630 ];
631 (values.iter().all(|value| value.is_finite())
632 && (0.0..=1.0).contains(&self.gain)
633 && self.start_dp >= 0.0
634 && self.end_dp >= self.start_dp)
635 .then_some(values)
636 }
637}
638
639#[derive(Clone, Copy, Debug, PartialEq)]
643pub struct GlassFaceTone {
644 pub black: f32,
646 pub white: f32,
648 pub saturation: f32,
650 pub max_luminance: f32,
652}
653
654impl GlassFaceTone {
655 fn uniforms(self) -> Option<[f32; 5]> {
656 let values = [
657 self.black,
658 self.white,
659 self.saturation,
660 self.max_luminance,
661 1.0,
662 ];
663 (values.iter().all(|value| value.is_finite())
664 && (0.0..=1.0).contains(&self.black)
665 && self.white > self.black
666 && self.saturation >= 0.0
667 && (0.0..=1.0).contains(&self.max_luminance))
668 .then_some(values)
669 }
670}
671
672#[derive(Clone, Debug, PartialEq)]
675pub struct Glass {
676 pub face_tone: Option<GlassFaceTone>,
678 pub variant: GlassVariant,
680 pub shape: LiquidShape,
682 pub tint: Option<Color>,
684 pub tint_transmission: Option<f32>,
686 pub tint_amount: Option<GlassTintAmount>,
688 pub pane_blur_radius: f32,
690 pub blur_radius: Option<f32>,
692 pub backdrop_blur: Option<(f32, f32)>,
694 pub saturation: Option<f32>,
696 pub refraction_depth: f32,
700 pub refraction_depth_dp: Option<f32>,
704 pub refraction: GlassRefraction,
706 pub refraction_curve: f32,
708 pub dispersion: f32,
710 pub edge_spectrum: Option<GlassSpectrum>,
712 pub transmission_refraction: f32,
716 pub meniscus_absorption: f32,
719 pub fold_depth: f32,
723 pub optical_zoom: f32,
726 pub rim_reflection: f32,
729 pub ink_recolor: Option<(Color, f32)>,
733 pub highlight: f32,
735 pub face_lighting: bool,
737 pub key_fill: Option<GlassKeyFill>,
739 pub contour_highlight: Option<GlassContourHighlight>,
741 pub specular_highlight: Option<GlassSpecularHighlight>,
743 pub face_response: Option<GlassFaceResponse>,
745 pub inner_shadow: Option<GlassShadow>,
747 pub lift: Option<f32>,
750 pub contrast: Option<f32>,
755 pub shadow: bool,
757 pub shadow_style: Option<GlassShadow>,
759 pub clip: bool,
762 pub foreground: Option<Color>,
765 pub adaptive_frost: f32,
767 pub adaptive_tone: bool,
770 pub resting_edge_sharpness: f32,
772}
773
774impl Glass {
775 pub fn regular() -> Self {
777 Self {
778 variant: GlassVariant::Regular,
779 face_tone: None,
780 shape: LiquidShape::Capsule,
781 tint: None,
782 tint_transmission: None,
783 tint_amount: None,
784 pane_blur_radius: 8.0,
785 blur_radius: None,
786 backdrop_blur: None,
787 saturation: None,
788 refraction_depth: 0.0,
789 refraction_depth_dp: None,
790 refraction: GlassRefraction::Radial,
791 refraction_curve: 0.25,
792 dispersion: 0.0,
793 edge_spectrum: None,
794 transmission_refraction: 0.0,
795 meniscus_absorption: 1.0,
796 fold_depth: 0.0,
797 optical_zoom: 1.0,
798 rim_reflection: 1.0,
799 ink_recolor: None,
800 highlight: 0.9,
801 face_lighting: true,
802 key_fill: None,
803 contour_highlight: None,
804 specular_highlight: None,
805 face_response: None,
806 inner_shadow: None,
807 lift: None,
808 contrast: None,
809 shadow: true,
810 shadow_style: None,
811 clip: true,
812 foreground: None,
813 adaptive_frost: 0.65,
814 adaptive_tone: false,
815 resting_edge_sharpness: 0.0,
816 }
817 }
818
819 pub fn clear() -> Self {
822 Self {
823 variant: GlassVariant::Clear,
824 ..Self::regular()
825 }
826 }
827
828 pub fn lens() -> Self {
831 Self {
832 variant: GlassVariant::Lens,
833 tint: Some(Color::rgba(1.0, 1.0, 1.0, 0.07)),
834 refraction_depth: 0.34,
835 refraction_curve: 1.0,
836 dispersion: 0.30,
837 transmission_refraction: 1.0,
838 highlight: 1.15,
839 adaptive_frost: 0.0,
840 ..Self::regular()
841 }
842 }
843
844 pub fn face_lighting(mut self, enabled: bool) -> Self {
846 self.face_lighting = enabled;
847 self
848 }
849
850 pub fn resting_edge_sharpness(mut self, sharpness: f32) -> Self {
853 self.resting_edge_sharpness = sharpness;
854 self
855 }
856
857 pub fn key_fill(mut self, lighting: GlassKeyFill) -> Self {
860 self.key_fill = Some(lighting);
861 self
862 }
863
864 pub fn face_response(mut self, response: GlassFaceResponse) -> Self {
866 self.face_response = Some(response);
867 self
868 }
869
870 pub fn inner_shadow(mut self, shadow: GlassShadow) -> Self {
874 self.inner_shadow = shadow.uniforms().map(|_| shadow);
875 self
876 }
877
878 pub fn shape(mut self, shape: LiquidShape) -> Self {
880 self.shape = shape;
881 self
882 }
883
884 pub fn tint(mut self, tint: Color) -> Self {
886 self.tint = Some(tint);
887 self
888 }
889
890 pub fn transmission_tint(mut self, tint: Color, black: f32) -> Self {
892 if black.is_finite() {
893 self.tint = Some(tint);
894 self.tint_transmission = Some(black.clamp(0.0, 1.0));
895 }
896 self
897 }
898
899 pub fn blur_radius(mut self, radius_dp: f32) -> Self {
901 self.blur_radius = Some(radius_dp);
902 self
903 }
904
905 pub fn saturation(mut self, saturation: f32) -> Self {
907 self.saturation = Some(saturation);
908 self
909 }
910
911 pub fn refraction_depth(mut self, fraction: f32) -> Self {
914 self.refraction_depth = fraction.clamp(0.0, 2.0);
915 self.refraction_depth_dp = None;
916 self
917 }
918
919 pub fn refraction_depth_dp(mut self, depth_dp: f32) -> Self {
922 self.refraction_depth_dp = Some(depth_dp.max(0.0));
923 self
924 }
925
926 pub fn surface_refraction(mut self, reach_dp: f32) -> Self {
931 self.refraction = GlassRefraction::Surface {
932 reach_dp: finite_nonnegative(reach_dp),
933 };
934 self
935 }
936
937 pub fn edge_spectrum(mut self, spectrum: GlassSpectrum) -> Self {
940 self.edge_spectrum = spectrum.uniforms().map(|_| spectrum);
941 self
942 }
943
944 pub fn edge_refraction(mut self, reach_dp: f32) -> Self {
948 self.refraction = GlassRefraction::EdgeLens {
949 reach_dp: finite_nonnegative(reach_dp),
950 };
951 self
952 }
953
954 pub fn refraction_curve(mut self, curve: f32) -> Self {
956 self.refraction_curve = curve.clamp(0.05, 1.0);
957 self
958 }
959
960 pub fn dispersion(mut self, strength: f32) -> Self {
962 self.dispersion = strength.clamp(0.0, 1.0);
963 self
964 }
965
966 pub fn transmission_refraction(mut self, strength: f32) -> Self {
969 self.transmission_refraction = strength.clamp(0.0, 1.0);
970 self
971 }
972
973 pub fn meniscus_absorption(mut self, strength: f32) -> Self {
975 self.meniscus_absorption = strength.clamp(0.0, 1.0);
976 self
977 }
978
979 pub fn fold_depth(mut self, depth_dp: f32) -> Self {
981 self.fold_depth = depth_dp.max(0.0);
982 self
983 }
984
985 pub fn optical_zoom(mut self, zoom: f32) -> Self {
987 self.optical_zoom = zoom.max(1.0);
988 self
989 }
990
991 pub fn rim_reflection(mut self, reflectivity: f32) -> Self {
993 self.rim_reflection = reflectivity.clamp(0.0, 2.0);
994 self
995 }
996
997 pub fn ink_recolor(mut self, color: Color, strength: f32) -> Self {
1000 self.ink_recolor = Some((color, strength.clamp(0.0, 1.0)));
1001 self
1002 }
1003
1004 pub fn highlight(mut self, highlight: f32) -> Self {
1006 self.highlight = highlight;
1007 self
1008 }
1009
1010 pub fn lift(mut self, lift: f32) -> Self {
1013 self.lift = Some(lift);
1014 self
1015 }
1016
1017 pub fn contrast(mut self, contrast: f32) -> Self {
1020 self.contrast = Some(contrast.max(0.05));
1021 self
1022 }
1023
1024 pub fn adaptive_frost(mut self, foreground: Color, strength: f32) -> Self {
1027 self.foreground = Some(foreground);
1028 self.adaptive_frost = strength.clamp(0.0, 1.0);
1029 self
1030 }
1031
1032 pub fn adaptive_tone(mut self, foreground: Color) -> Self {
1038 self.face_tone = None;
1039 self.foreground = Some(foreground);
1040 self.adaptive_tone = true;
1041 self.adaptive_frost = 0.0;
1042 self
1043 }
1044
1045 pub fn face_tone(mut self, tone: GlassFaceTone) -> Self {
1048 self.face_tone = tone.uniforms().map(|_| tone);
1049 self.adaptive_tone = false;
1050 self.adaptive_frost = 0.0;
1051 self
1052 }
1053
1054 pub fn shadow(mut self, shadow: bool) -> Self {
1056 self.shadow = shadow;
1057 self
1058 }
1059
1060 pub fn shadow_style(mut self, shadow: GlassShadow) -> Self {
1062 self.shadow_style = Some(shadow);
1063 self
1064 }
1065
1066 pub fn no_clip(mut self) -> Self {
1069 self.clip = false;
1070 self
1071 }
1072
1073 pub fn tint_amount(mut self, amount: GlassTintAmount) -> Self {
1076 self.tint_amount = Some(amount);
1077 self
1078 }
1079
1080 pub fn pane_blur_radius(mut self, radius: f32) -> Self {
1082 if radius.is_finite() && radius >= 0.0 {
1083 self.pane_blur_radius = radius;
1084 }
1085 self
1086 }
1087
1088 pub fn backdrop_blur(mut self, radius_dp: f32, opacity: f32) -> Self {
1092 self.backdrop_blur = (radius_dp.is_finite() && opacity.is_finite())
1093 .then_some((radius_dp.max(0.0), opacity.clamp(0.0, 1.0)));
1094 self
1095 }
1096
1097 fn default_blur_radius(&self) -> f32 {
1098 match self.variant {
1099 GlassVariant::Regular => 8.0,
1100 GlassVariant::Clear => 3.0,
1101 GlassVariant::Lens => 0.0,
1102 }
1103 }
1104
1105 fn default_saturation(&self) -> f32 {
1106 match self.variant {
1107 GlassVariant::Regular => 1.5,
1108 GlassVariant::Clear => 1.25,
1109 GlassVariant::Lens => 1.0,
1110 }
1111 }
1112
1113 pub fn backdrop_effect(
1117 &self,
1118 colors: &LiquidColors,
1119 density: f32,
1120 dynamics: GlassDynamics,
1121 ) -> RenderEffect {
1122 self.resolve(colors).backdrop_effect(density, dynamics)
1123 }
1124
1125 fn preferred_foreground(&self, colors: &LiquidColors) -> Color {
1126 self.foreground
1127 .filter(|color| {
1128 [color.r(), color.g(), color.b(), color.a()]
1129 .iter()
1130 .all(|value| value.is_finite())
1131 })
1132 .unwrap_or(colors.label)
1133 }
1134
1135 pub(crate) fn resolve(&self, colors: &LiquidColors) -> ResolvedGlass {
1136 let lift = self.lift.unwrap_or(match (self.variant, colors.is_dark) {
1137 (GlassVariant::Regular, false) => 0.42,
1138 (GlassVariant::Regular, true) => -0.38,
1139 (GlassVariant::Clear, false) => 0.12,
1140 (GlassVariant::Clear, true) => -0.12,
1141 (GlassVariant::Lens, false) => 0.10,
1142 (GlassVariant::Lens, true) => -0.08,
1143 });
1144 let foreground = self.preferred_foreground(colors);
1145 let shadow = self.shadow_style.unwrap_or_else(|| {
1146 GlassShadow::new(
1147 Color::BLACK.with_alpha(match (self.variant, colors.is_dark) {
1148 (GlassVariant::Lens, false) => 0.14,
1149 (GlassVariant::Lens, true) => 0.28,
1150 (_, false) => 0.16,
1151 (_, true) => 0.5,
1152 }),
1153 if self.variant == GlassVariant::Lens {
1154 10.0
1155 } else {
1156 22.0
1157 },
1158 if self.variant == GlassVariant::Lens {
1159 3.0
1160 } else {
1161 8.0
1162 },
1163 if self.variant == GlassVariant::Lens {
1164 -6.0
1165 } else {
1166 -2.0
1167 },
1168 )
1169 });
1170 ResolvedGlass {
1171 face_tone: self.face_tone,
1172 shape: self.shape,
1173 tint: self.tint.unwrap_or(colors.glass_tint),
1174 tint_transmission: self.tint_transmission,
1175 tint_amount: self.tint_amount,
1176 pane_blur_radius: self.pane_blur_radius,
1177 backdrop_blur: self.backdrop_blur,
1178 blur_radius_dp: self
1179 .blur_radius
1180 .unwrap_or_else(|| self.default_blur_radius()),
1181 saturation: self.saturation.unwrap_or_else(|| self.default_saturation()),
1182 refraction_depth: self.refraction_depth,
1183 refraction_depth_dp: self.refraction_depth_dp,
1184 refraction: self.refraction,
1185 refraction_curve: self.refraction_curve,
1186 dispersion: self.dispersion,
1187 edge_spectrum: self.edge_spectrum,
1188 transmission_refraction: self.transmission_refraction,
1189 meniscus_absorption: self.meniscus_absorption,
1190 fold_depth: self.fold_depth,
1191 optical_zoom: self.optical_zoom,
1192 rim_reflection: self.rim_reflection,
1193 ink_recolor: self.ink_recolor,
1194 highlight: self.highlight,
1195 face_lighting: self.face_lighting,
1196 key_fill: self.key_fill,
1197 contour_highlight: self.contour_highlight,
1198 specular_highlight: self.specular_highlight,
1199 face_response: self.face_response,
1200 inner_shadow: self.inner_shadow,
1201 lift,
1202 contrast: self.contrast.unwrap_or(match self.variant {
1203 GlassVariant::Lens => 1.0,
1204 _ => 1.03,
1205 }),
1206 shadow: self.shadow,
1207 clip: self.clip,
1208 foreground_luma: 0.2126 * foreground.r()
1209 + 0.7152 * foreground.g()
1210 + 0.0722 * foreground.b(),
1211 adaptive_frost: self.adaptive_frost,
1212 adaptive_tone: self.adaptive_tone,
1213 resting_edge_sharpness: finite_nonnegative(self.resting_edge_sharpness).min(1.0),
1214 rim_style: if self.variant == GlassVariant::Lens {
1215 1.0
1216 } else {
1217 0.0
1218 },
1219 shadow_color: shadow.color,
1220 shadow_radius: shadow.radius,
1221 shadow_offset_y: shadow.offset_y,
1222 shadow_spread: shadow.spread,
1223 }
1224 }
1225}
1226
1227impl Default for Glass {
1228 fn default() -> Self {
1229 Self::regular()
1230 }
1231}
1232
1233#[derive(Clone, Debug, PartialEq)]
1234pub(crate) struct ResolvedGlass {
1235 specular_highlight: Option<GlassSpecularHighlight>,
1236 face_tone: Option<GlassFaceTone>,
1237 resting_edge_sharpness: f32,
1238 pub shape: LiquidShape,
1239 pub tint: Color,
1240 tint_transmission: Option<f32>,
1241 tint_amount: Option<GlassTintAmount>,
1242 pane_blur_radius: f32,
1243 backdrop_blur: Option<(f32, f32)>,
1244 pub blur_radius_dp: f32,
1245 pub saturation: f32,
1246 pub refraction_depth: f32,
1247 pub refraction_depth_dp: Option<f32>,
1248 pub refraction: GlassRefraction,
1249 pub refraction_curve: f32,
1250 pub dispersion: f32,
1251 pub edge_spectrum: Option<GlassSpectrum>,
1252 pub transmission_refraction: f32,
1253 pub meniscus_absorption: f32,
1254 pub fold_depth: f32,
1255 pub optical_zoom: f32,
1256 pub rim_reflection: f32,
1257 pub ink_recolor: Option<(Color, f32)>,
1258 pub highlight: f32,
1259 pub face_lighting: bool,
1260 pub key_fill: Option<GlassKeyFill>,
1261 pub contour_highlight: Option<GlassContourHighlight>,
1263 pub face_response: Option<GlassFaceResponse>,
1265 pub lift: f32,
1266 pub inner_shadow: Option<GlassShadow>,
1268 pub contrast: f32,
1269 pub shadow: bool,
1270 pub clip: bool,
1271 pub rim_style: f32,
1272 pub foreground_luma: f32,
1273 pub adaptive_frost: f32,
1274 pub adaptive_tone: bool,
1275 pub shadow_color: Color,
1276 pub shadow_radius: f32,
1277 pub shadow_offset_y: f32,
1278 pub shadow_spread: f32,
1279}
1280
1281fn set_edge_return_depth(shader: &mut RuntimeShader, depth: Option<f32>) {
1282 if let Some(depth) = depth.filter(|depth| depth.is_finite() && *depth >= 0.0) {
1283 shader.set_float2(GLASS_EDGE_RETURN_DEPTH_UNIFORM, depth, 1.0);
1284 }
1285}
1286
1287impl ResolvedGlass {
1288 fn placeholder_shape(&self, morph: Option<&GlassMorph>) -> PlaceholderShape {
1291 let Some(morph) = morph else {
1292 return PlaceholderShape {
1293 bounds: Rect {
1294 x: 0.0,
1295 y: 0.0,
1296 width: 1.0,
1297 height: 1.0,
1298 },
1299 corner_radius: self.shape.radius_dp(),
1300 };
1301 };
1302 let (node_width, node_height) = morph.node_size;
1303 let (node_width, node_height) =
1304 (node_width.max(f32::EPSILON), node_height.max(f32::EPSILON));
1305 let added = morph.shapes.iter().filter(|&&(.., radius)| radius > -1.5);
1307 let (left, top, right, bottom) = std::iter::once(&morph.primary).chain(added).fold(
1308 (f32::MAX, f32::MAX, f32::MIN, f32::MIN),
1309 |(left, top, right, bottom), &(center_x, center_y, width, height, _)| {
1310 (
1311 left.min(center_x - width * 0.5),
1312 top.min(center_y - height * 0.5),
1313 right.max(center_x + width * 0.5),
1314 bottom.max(center_y + height * 0.5),
1315 )
1316 },
1317 );
1318 let (.., radius) = morph.primary;
1320 PlaceholderShape {
1321 bounds: Rect {
1322 x: left / node_width,
1323 y: top / node_height,
1324 width: (right - left) / node_width,
1325 height: (bottom - top) / node_height,
1326 },
1327 corner_radius: if radius < 0.0 { f32::MAX } else { radius },
1328 }
1329 }
1330
1331 pub(crate) fn without_transparency(mut self, colors: &LiquidColors) -> Self {
1335 self.tint = colors.surface;
1336 self.tint_transmission = None;
1337 self.tint_amount = None;
1338 self.backdrop_blur = None;
1339 self.face_tone = None;
1340 self.contour_highlight = None;
1341 self.specular_highlight = None;
1342 self.adaptive_tone = false;
1343 self.blur_radius_dp = 0.0;
1344 self.saturation = 1.0;
1345 self.refraction_depth = 0.0;
1346 self.refraction_depth_dp = None;
1347 self.dispersion = 0.0;
1348 self.edge_spectrum = None;
1349 self.transmission_refraction = 0.0;
1350 self.meniscus_absorption = 0.0;
1351 self.rim_reflection = 0.0;
1352 self.adaptive_frost = 0.0;
1353 self
1354 }
1355
1356 fn clips_in_shader(&self) -> bool {
1357 self.key_fill.and_then(GlassKeyFill::uniforms).is_some()
1358 }
1359 pub(crate) fn backdrop_effect(&self, density: f32, dynamics: GlassDynamics) -> RenderEffect {
1360 self.runtime_effect(density, &dynamics, false)
1361 }
1362
1363 fn blur_radii(&self, density: f32, activity: f32, content_mask: bool) -> (f32, f32) {
1364 if content_mask || self.tint_amount.is_some() {
1365 return (0.0, 0.0);
1366 }
1367 let radius = self.blur_radius_dp * density * activity;
1368 if radius > WCKSRD_OPTICAL_BLUR_RADIUS_PX {
1369 (0.0, radius)
1370 } else {
1371 (radius, 0.0)
1372 }
1373 }
1374
1375 fn runtime_effect(
1376 &self,
1377 density: f32,
1378 dynamics: &GlassDynamics,
1379 content_mask: bool,
1380 ) -> RenderEffect {
1381 self.runtime_effect_with_content(density, dynamics, content_mask, false)
1382 }
1383
1384 fn set_refraction_uniforms(
1385 &self,
1386 shader: &mut RuntimeShader,
1387 dynamics: &GlassDynamics,
1388 activity: f32,
1389 ) -> (f32, f32, f32) {
1390 let press_depth = dynamics.press_depth.unwrap_or(1.0).clamp(0.0, 1.0);
1391 shader.set_float(9, self.refraction_depth * activity * press_depth);
1392 let refraction = dynamics.refraction.unwrap_or(self.refraction);
1393 let (refraction_mode, refraction_reach) = refraction.shader_parameters();
1394 let refraction_weight = if matches!(refraction, GlassRefraction::LayeredSurface { .. }) {
1395 1.0
1396 } else {
1397 activity * press_depth
1398 };
1399 shader.set_float(GLASS_REFRACTION_MODE_UNIFORM, refraction_mode);
1400 if let GlassRefraction::LayeredSurface {
1401 return_reach_dp,
1402 return_depth_dp,
1403 ..
1404 } = refraction
1405 {
1406 shader.set_float2(
1407 GLASS_LAYERED_RETURN_UNIFORM,
1408 finite_nonnegative(return_reach_dp),
1409 finite_nonnegative(return_depth_dp),
1410 );
1411 }
1412 set_edge_return_depth(shader, dynamics.edge_return_depth_dp);
1413 shader.set_float(
1414 GLASS_EDGE_REFRACTION_REACH_UNIFORM,
1415 refraction_reach * refraction_weight,
1416 );
1417 shader.set_float(
1418 GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM,
1419 self.refraction_depth_dp.unwrap_or(0.0) * refraction_weight,
1420 );
1421 shader.set_float(
1422 GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
1423 if self.refraction_depth_dp.is_some() {
1424 1.0
1425 } else {
1426 0.0
1427 },
1428 );
1429 shader.set_float(
1430 GLASS_REFRACTION_CURVE_UNIFORM,
1431 self.refraction_curve * activity,
1432 );
1433 shader.set_float(
1434 GLASS_DISPERSION_UNIFORM,
1435 self.dispersion * activity * press_depth,
1436 );
1437 let spectrum_reach = self.set_spectrum_uniforms(shader);
1438 shader.set_float(
1439 GLASS_TRANSMISSION_REFRACTION_UNIFORM,
1440 self.transmission_refraction * if refraction_mode > 2.5 { 1.0 } else { activity },
1441 );
1442 shader.set_float(
1443 GLASS_MENISCUS_ABSORPTION_UNIFORM,
1444 self.meniscus_absorption * press_depth,
1445 );
1446 shader.set_float(
1447 GLASS_FOLD_DEPTH_UNIFORM,
1448 self.fold_depth.max(0.0) * press_depth,
1449 );
1450 (refraction_mode, refraction_reach, spectrum_reach)
1451 }
1452
1453 fn set_shadow_uniforms(
1454 &self,
1455 shader: &mut RuntimeShader,
1456 dynamics: &GlassDynamics,
1457 activity: f32,
1458 ) -> (f32, f32) {
1459 let dynamic_shadow = !self.clip && self.shadow;
1460 let shadow = dynamics
1461 .shadow
1462 .filter(|shadow| shadow.uniforms().is_some())
1463 .unwrap_or_else(|| {
1464 GlassShadow::new(
1465 self.shadow_color
1466 .with_alpha(self.shadow_color.a() * 0.55 * activity),
1467 self.shadow_radius,
1468 self.shadow_offset_y,
1469 self.shadow_spread,
1470 )
1471 });
1472 shader.set_float(
1473 102,
1474 if dynamic_shadow {
1475 shadow.color.a()
1476 } else {
1477 0.0
1478 },
1479 );
1480 shader.set_float(103, shadow.radius);
1481 shader.set_float(104, shadow.offset_y);
1482 shader.set_float(105, shadow.spread);
1483 shader.set_float4(
1484 cranpose_ui_graphics::GLASS_SHADOW_COLOR_UNIFORM,
1485 shadow.color.r(),
1486 shadow.color.g(),
1487 shadow.color.b(),
1488 0.0,
1489 );
1490 let ring_shadow = dynamics.ring_shadow.filter(|(ring, width)| {
1491 ring.color.a() > 0.0 && ring.uniforms().is_some() && width.is_finite() && *width > 0.0
1492 });
1493 if let Some((ring, width)) = ring_shadow {
1494 shader.set_float4(
1495 cranpose_ui_graphics::GLASS_RING_SHADOW_UNIFORM,
1496 ring.color.r(),
1497 ring.color.g(),
1498 ring.color.b(),
1499 ring.color.a(),
1500 );
1501 shader.set_float4(
1502 cranpose_ui_graphics::GLASS_RING_SHADOW_UNIFORM + 4,
1503 ring.radius,
1504 ring.offset_y,
1505 width,
1506 1.0,
1507 );
1508 shader.set_float(
1509 cranpose_ui_graphics::GLASS_RING_SHADOW_UNIFORM + 8,
1510 ring.spread,
1511 );
1512 }
1513 let ring_reach = ring_shadow.map_or(0.0, |(ring, width)| {
1514 3.0 * ring.radius + ring.offset_y.abs() + width * 0.5 + ring.spread.abs()
1515 });
1516 let contour_reach = self
1517 .contour_highlight
1518 .and_then(GlassContourHighlight::uniforms)
1519 .filter(|values| values[2] > 0.0 && activity > 0.0)
1520 .map_or(0.0, |values| (-values[1]).max(0.0));
1521 let visible_shadow = dynamic_shadow && shadow.color.a() > 0.0;
1522 let shadow_reach = if visible_shadow {
1523 shadow.radius + shadow.offset_y.abs() + shadow.spread.max(0.0)
1524 } else {
1525 0.0
1526 }
1527 .max(ring_reach)
1528 .max(contour_reach);
1529 shader.set_output_padding(shadow_reach);
1532 let offset = if visible_shadow || ring_shadow.is_some() {
1533 shadow
1534 .offset_y
1535 .abs()
1536 .max(ring_shadow.map_or(0.0, |(ring, _)| ring.offset_y.abs()))
1537 } else {
1538 0.0
1539 };
1540 (shadow_reach, offset)
1541 }
1542
1543 fn runtime_effect_with_content(
1544 &self,
1545 density: f32,
1546 dynamics: &GlassDynamics,
1547 content_mask: bool,
1548 separate_content: bool,
1549 ) -> RenderEffect {
1550 let density = density.max(f32::EPSILON);
1551 let activity = dynamics
1552 .activity
1553 .filter(|value| value.is_finite())
1554 .unwrap_or(1.0)
1555 .clamp(0.0, 1.0);
1556 static SHADER: OnceLock<RuntimeShader> = OnceLock::new();
1557 let mut shader = SHADER
1558 .get_or_init(|| {
1559 let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
1560 shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
1561 shader.set_float2(GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM, 0.0, 0.0);
1562 specialize_liquid_glass(&mut shader);
1563 shader
1564 })
1565 .clone();
1566 if let Some(morph) = dynamics.morph.as_ref() {
1567 set_morph_uniforms(&mut shader, morph, activity);
1568 } else {
1569 shader.set_float2(0, 0.0, 0.0);
1570 shader.set_float(6, self.shape.shader_radius_px(density));
1571 }
1572 let projection = dynamics.projection();
1573 shader.set_float2(GLASS_OPTICAL_PROJECTION_UNIFORM, projection.0, projection.1);
1574 let (refraction_mode, refraction_reach, spectrum_reach) =
1575 self.set_refraction_uniforms(&mut shader, dynamics, activity);
1576 shader.set_float(
1577 GLASS_OPTICAL_ZOOM_UNIFORM,
1578 1.0 + (self.optical_zoom - 1.0).max(0.0) * activity,
1579 );
1580 let zoom_anchor = dynamics
1581 .morph
1582 .as_ref()
1583 .map_or((0.0, 0.0), |morph| morph.zoom_anchor);
1584 shader.set_float2(
1585 GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
1586 zoom_anchor.0,
1587 zoom_anchor.1,
1588 );
1589 shader.set_float(121, self.rim_reflection.max(0.001));
1590 let (ink_color, ink_strength) = self
1591 .ink_recolor
1592 .map_or((Color::TRANSPARENT, 0.0), |(color, strength)| {
1593 (color, strength * activity)
1594 });
1595 shader.set_float(124, ink_color.r());
1596 shader.set_float(125, ink_color.g());
1597 shader.set_float(126, ink_color.b());
1598 shader.set_float(127, ink_strength);
1599 let (light_x, light_y) = glass_light_direction();
1600 shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM, light_x);
1601 shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM + 1, light_y);
1602 self.set_lighting_uniforms(&mut shader, dynamics);
1603 shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, density);
1604 shader.set_float(
1605 11,
1606 (self.highlight + dynamics.highlight_boost).clamp(0.0, 2.0) * activity,
1607 );
1608 let dynamic_tint = boost_tint_saturation(self.tint, dynamics.saturation_boost);
1609 let dynamic_tint_alpha = (dynamic_tint.a()
1610 * dynamics
1611 .tint_alpha_multiplier
1612 .unwrap_or(1.0)
1613 .clamp(0.0, 2.0)
1614 * activity)
1615 .clamp(0.0, 1.0);
1616 shader.set_float4(
1617 14,
1618 dynamic_tint.r(),
1619 dynamic_tint.g(),
1620 dynamic_tint.b(),
1621 dynamic_tint_alpha,
1622 );
1623 if let Some((color, progress)) = dynamics.tint_crossfade.filter(|(_, p)| p.is_finite()) {
1624 let index = cranpose_ui_graphics::liquid_glass::GLASS_TINT_CROSSFADE_UNIFORM;
1625 shader.set_float(index, 1.0 + progress.clamp(0.0, 1.0));
1626 shader.set_float4(index + 1, color.r(), color.g(), color.b(), color.a());
1627 }
1628 let saturation = (self.saturation + dynamics.saturation_boost).max(0.0);
1629 shader.set_float(18, 1.0 + (saturation - 1.0) * activity);
1630 shader.set_float(20, self.lift * activity);
1631 shader.set_float(21, 0.5 * activity);
1632 shader.set_float2(22, 0.0, 1.0);
1633 shader.set_float(24, 1.0 + (self.contrast - 1.0) * activity);
1634 shader.set_float(28, self.rim_style * activity);
1635 let (wcksrd_blur_radius, gaussian_blur_radius) =
1636 self.blur_radii(density, activity, content_mask);
1637 shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, wcksrd_blur_radius);
1638 shader.set_float(GLASS_ACTIVITY_UNIFORM, activity);
1639 shader.set_float(
1640 GLASS_RESTING_EDGE_SHARPNESS_UNIFORM,
1641 self.resting_edge_sharpness,
1642 );
1643 let resting_tint = dynamics.resting_tint.unwrap_or(Color::TRANSPARENT);
1644 shader.set_float4(
1645 GLASS_RESTING_TINT_UNIFORM,
1646 resting_tint.r(),
1647 resting_tint.g(),
1648 resting_tint.b(),
1649 resting_tint.a(),
1650 );
1651 shader.set_float(112, if content_mask { 1.0 } else { 0.0 });
1652 shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, self.adaptive_frost * activity);
1653 shader.set_float(97, self.foreground_luma);
1654 shader.set_float(GLASS_ADAPTIVE_TONE_UNIFORM, f32::from(self.adaptive_tone));
1655 self.set_backdrop_uniforms(&mut shader, density, content_mask);
1656 shader.set_float(
1657 cranpose_ui_graphics::GLASS_FOREGROUND_CONTENT_UNIFORM,
1658 f32::from(separate_content),
1659 );
1660 let (shadow_reach, shadow_offset) = if content_mask {
1661 (0.0, 0.0)
1662 } else {
1663 self.set_shadow_uniforms(&mut shader, dynamics, activity)
1664 };
1665 let morph_pad = dynamics.morph.as_ref().map_or(0.0, |morph| {
1666 let (px, py, pw, ph, _) = morph.primary;
1667 let (left, top) = (px - pw * 0.5, py - ph * 0.5);
1668 let (right, bottom) = (px + pw * 0.5, py + ph * 0.5);
1669 let mut shape_reach = 0.0f32;
1670 for (sx, sy, sw, sh, _) in &morph.shapes {
1671 let reach_x = ((sx + sw * 0.5) - right)
1672 .max(left - (sx - sw * 0.5))
1673 .max(0.0);
1674 let reach_y = ((sy + sh * 0.5) - bottom)
1675 .max(top - (sy - sh * 0.5))
1676 .max(0.0);
1677 shape_reach = shape_reach.max(reach_x.max(reach_y));
1678 }
1679 let glue_pad = if morph.shapes.is_empty() {
1680 0.0
1681 } else {
1682 morph.glue * 2.0
1683 };
1684 morph.wobble_amplitude * 2.0 + morph.bulge_amplitude + shape_reach + glue_pad
1685 });
1686 shader.set_input_padding(if content_mask {
1687 0.0
1688 } else {
1689 (self.input_padding()
1690 + if refraction_mode > 2.5 {
1691 refraction_reach + spectrum_reach
1692 } else if refraction_mode >= 1.5 {
1693 refraction_reach * (26.5 / 9.0) + spectrum_reach
1694 } else {
1695 refraction_reach
1696 }
1697 + morph_pad
1698 + wcksrd_blur_radius / density)
1699 * projection.0.max(projection.1).max(1.0)
1700 });
1701 if let Some(morph) = dynamics.morph.as_ref() {
1702 let support =
1703 morph_output_support(morph, activity, morph_pad, shadow_reach, shadow_offset);
1704 let (cx, cy, _, _, _) = morph.primary;
1705 let support = Rect {
1706 x: cx + (support.x - cx) * projection.0,
1707 y: cy + (support.y - cy) * projection.1,
1708 width: support.width * projection.0,
1709 height: support.height * projection.1,
1710 };
1711 let (node_w, node_h) = morph.node_size;
1712 let overhang = (-support.x)
1713 .max(-support.y)
1714 .max(support.x + support.width - node_w)
1715 .max(support.y + support.height - node_h)
1716 .max(0.0);
1717 shader.set_output_padding((morph_pad + shadow_reach + 4.0).max(overhang));
1718 shader.set_output_support(Some(support));
1719 }
1720
1721 shader.set_placeholder(Some(liquid_glass_placeholder(
1722 shown_tint(
1723 Color(
1724 dynamic_tint.r(),
1725 dynamic_tint.g(),
1726 dynamic_tint.b(),
1727 dynamic_tint_alpha,
1728 ),
1729 dynamics.tint_crossfade,
1730 ),
1731 Some(self.placeholder_shape(dynamics.morph.as_ref())),
1732 )));
1733 let optical_effect = liquid_glass_runtime_effect(shader);
1734 if gaussian_blur_radius > f32::EPSILON {
1735 RenderEffect::blur_with_edge_treatment(gaussian_blur_radius, TileMode::Mirror)
1736 .then(optical_effect)
1737 } else {
1738 optical_effect
1739 }
1740 }
1741
1742 fn set_backdrop_uniforms(&self, shader: &mut RuntimeShader, density: f32, content_mask: bool) {
1743 if let Some(values) = self.face_tone.and_then(GlassFaceTone::uniforms) {
1744 for (index, value) in values.into_iter().enumerate() {
1745 shader.set_float(cranpose_ui_graphics::GLASS_FACE_TONE_UNIFORM + index, value);
1746 }
1747 }
1748 if let Some(amount) = self.tint_amount {
1749 shader.set_float(
1750 cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM,
1751 self.pane_blur_radius * density,
1752 );
1753 shader.set_float(
1754 cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM + 1,
1755 amount.percent() / 100.0,
1756 );
1757 }
1758 if !content_mask && let Some((radius, opacity)) = self.backdrop_blur {
1759 shader.set_float2(
1760 cranpose_ui_graphics::GLASS_BACKDROP_BLUR_UNIFORM,
1761 radius * density,
1762 opacity,
1763 );
1764 }
1765 }
1766
1767 fn set_spectrum_uniforms(&self, shader: &mut RuntimeShader) -> f32 {
1768 let spectrum = self
1769 .edge_spectrum
1770 .and_then(|spectrum| spectrum.uniforms().map(|values| (spectrum, values)));
1771 if let Some((spectrum, values)) = spectrum {
1772 for (index, value) in values.into_iter().enumerate() {
1773 shader.set_float(GLASS_EDGE_SPECTRUM_UNIFORM + index, value);
1774 }
1775 spectrum.capture_reach()
1776 } else {
1777 40.787 * self.dispersion
1778 }
1779 }
1780
1781 fn set_lighting_uniforms(&self, shader: &mut RuntimeShader, dynamics: &GlassDynamics) {
1782 if let Some(values) = self
1783 .specular_highlight
1784 .and_then(GlassSpecularHighlight::uniforms)
1785 {
1786 for (index, value) in values.into_iter().enumerate() {
1787 shader.set_float(
1788 cranpose_ui_graphics::GLASS_SPECULAR_HIGHLIGHT_UNIFORM + index,
1789 value,
1790 );
1791 }
1792 }
1793 shader.set_float(
1794 cranpose_ui_graphics::GLASS_CONTACT_LIGHT_UNIFORM,
1795 f32::from(dynamics.contact_lighting),
1796 );
1797 if let Some(black) = self.tint_transmission.filter(|value| value.is_finite()) {
1798 shader.set_float2(
1799 cranpose_ui_graphics::GLASS_TINT_TRANSMISSION_UNIFORM,
1800 black.clamp(0.0, 1.0),
1801 1.0,
1802 );
1803 }
1804 let (touch_x, touch_y, touch_intensity) = dynamics.touch.unwrap_or((0.0, 0.0, 0.0));
1805 shader.set_float(118, touch_x);
1806 shader.set_float(119, touch_y);
1807 shader.set_float(120, touch_intensity.clamp(0.0, 1.0));
1808 shader.set_float(
1809 GLASS_FACE_LIGHTING_OFF_UNIFORM,
1810 f32::from(!self.face_lighting),
1811 );
1812 shader.set_float(
1813 GLASS_TOUCH_RADIUS_UNIFORM,
1814 dynamics
1815 .touch_radius_dp
1816 .filter(|radius| radius.is_finite() && *radius > 0.0)
1817 .unwrap_or(58.0),
1818 );
1819 shader.set_float(
1820 GLASS_LAYER_CLIPPED_UNIFORM,
1821 f32::from(self.clip && !self.clips_in_shader()),
1822 );
1823 if let Some(values) = self.key_fill.and_then(GlassKeyFill::uniforms) {
1824 for (index, value) in values.into_iter().enumerate() {
1825 shader.set_float(GLASS_KEY_FILL_UNIFORM + index, value);
1826 }
1827 }
1828 if let Some(values) = self.face_response.and_then(GlassFaceResponse::uniforms) {
1829 for (index, value) in values.into_iter().enumerate() {
1830 shader.set_float(GLASS_FACE_RESPONSE_UNIFORM + index, value);
1831 }
1832 }
1833 if let Some(values) = self
1834 .contour_highlight
1835 .and_then(GlassContourHighlight::uniforms)
1836 {
1837 for (index, value) in values.into_iter().enumerate() {
1838 shader.set_float(
1839 cranpose_ui_graphics::GLASS_CONTOUR_HIGHLIGHT_UNIFORM + index,
1840 value,
1841 );
1842 }
1843 }
1844 if let Some(values) = self.inner_shadow.and_then(GlassShadow::uniforms) {
1845 for (index, value) in values.into_iter().enumerate() {
1846 shader.set_float(GLASS_INNER_SHADOW_UNIFORM + index, value);
1847 }
1848 }
1849 }
1850
1851 fn input_padding(&self) -> f32 {
1852 2.0
1853 }
1854}
1855
1856pub trait LiquidModifierExt {
1858 fn glass_effect(self, glass: Glass) -> Modifier;
1864
1865 fn glass_effect_with(
1869 self,
1870 glass: Glass,
1871 dynamics: impl Fn() -> GlassDynamics + 'static,
1872 ) -> Modifier;
1873}
1874
1875impl LiquidModifierExt for Modifier {
1876 fn glass_effect(self, glass: Glass) -> Modifier {
1877 self.glass_effect_with(glass, GlassDynamics::default)
1878 }
1879
1880 fn glass_effect_with(
1881 self,
1882 glass: Glass,
1883 dynamics: impl Fn() -> GlassDynamics + 'static,
1884 ) -> Modifier {
1885 let colors = crate::theme::liquid_colors();
1886 let options = cranpose_services::local_accessibility_options().current();
1887 let resolved = if options.reduce_transparency {
1888 glass.resolve(&colors).without_transparency(&colors)
1889 } else {
1890 glass.resolve(&colors)
1891 };
1892 let shape = resolved.shape;
1893
1894 let mut modifier = self;
1895 if resolved.shadow && resolved.clip {
1896 let shadow_color = resolved.shadow_color;
1897 let (radius, offset_y, spread) = (
1898 resolved.shadow_radius,
1899 resolved.shadow_offset_y,
1900 resolved.shadow_spread,
1901 );
1902 modifier = modifier.drop_shadow(shape.layer_shape(), move |scope| {
1903 scope.radius = radius;
1904 scope.spread = spread;
1905 scope.offset.y = offset_y;
1906 scope.color = shadow_color;
1907 scope.cutout = true;
1908 });
1909 }
1910
1911 let layer = cached_glass_layer(resolved);
1912 modifier.graphics_layer(move || layer(current_density(), dynamics()))
1913 }
1914}
1915
1916struct CachedGlassLayer<T> {
1917 density: u32,
1918 light: [u32; 2],
1919 dynamics: GlassDynamics,
1920 value: T,
1921}
1922
1923fn cached_glass_layer(resolved: ResolvedGlass) -> impl Fn(f32, GlassDynamics) -> GraphicsLayer {
1924 cached_glass_layers(resolved, false, std::convert::identity)
1925}
1926
1927pub(crate) fn cached_glass_content_layers(
1928 resolved: ResolvedGlass,
1929) -> impl Fn(f32, GlassDynamics) -> [GraphicsLayer; 2] {
1930 cached_glass_layers(resolved, true, |mut layer| {
1931 let Some(RenderEffect::Chain { first, second }) = layer.backdrop_effect.take() else {
1932 panic!("content layers require a sequential edge lens");
1933 };
1934 layer.backdrop_effect = Some((*second).clone());
1935 [
1936 GraphicsLayer {
1937 backdrop_effect: Some((*first).clone()),
1938 ..Default::default()
1939 },
1940 layer,
1941 ]
1942 })
1943}
1944
1945fn cached_glass_layers<T: Clone>(
1946 resolved: ResolvedGlass,
1947 separate_content: bool,
1948 assemble: impl Fn(GraphicsLayer) -> T,
1949) -> impl Fn(f32, GlassDynamics) -> T {
1950 let cached = RefCell::new(None::<CachedGlassLayer<T>>);
1951 move |density, dynamics| {
1952 let direction = glass_light_direction();
1953 let light = [direction.0, direction.1].map(f32::to_bits);
1954 let mut cached = cached.borrow_mut();
1955 if let Some(previous) = &*cached
1956 && previous.density == density.to_bits()
1957 && previous.light == light
1958 && glass_dynamics_match(&previous.dynamics, &dynamics)
1959 {
1960 return previous.value.clone();
1961 }
1962 let render_effect = ((!resolved.clip && dynamics.morph.is_some())
1963 || (resolved.clip && resolved.clips_in_shader()))
1964 .then(|| resolved.runtime_effect(density, &dynamics, true));
1965 let layer = GraphicsLayer {
1966 backdrop_effect: Some(resolved.runtime_effect_with_content(
1967 density,
1968 &dynamics,
1969 false,
1970 separate_content,
1971 )),
1972 render_effect,
1973 shape: resolved.shape.layer_shape(),
1974 clip: resolved.clip && !resolved.clips_in_shader(),
1975 ..Default::default()
1976 };
1977 let value = assemble(layer);
1978 *cached = Some(CachedGlassLayer {
1979 density: density.to_bits(),
1980 light,
1981 dynamics,
1982 value: value.clone(),
1983 });
1984 value
1985 }
1986}
1987
1988fn glass_dynamics_match(a: &GlassDynamics, b: &GlassDynamics) -> bool {
1989 let bits = |d: &GlassDynamics| {
1990 let GlassDynamics {
1991 contact_lighting,
1992 refraction,
1993 shadow,
1994 ring_shadow,
1995 edge_return_depth_dp,
1996 optical_projection,
1997 activity,
1998 resting_tint,
1999 highlight_boost,
2000 saturation_boost,
2001 tint_alpha_multiplier,
2002 tint_crossfade,
2003 morph: _,
2004 touch,
2005 touch_radius_dp,
2006 press_depth,
2007 } = d;
2008 (
2009 (
2010 *contact_lighting,
2011 ring_shadow.map(|(ring, width)| (shadow_bits(ring), width.to_bits())),
2012 tint_crossfade.map(|(Color(r, g, b, a), p)| [r, g, b, a, p].map(f32::to_bits)),
2013 ),
2014 refraction.map(|refraction| {
2015 match refraction {
2016 GlassRefraction::Radial => [0.0, 0.0, 0.0, 0.0],
2017 GlassRefraction::Surface { reach_dp } => [1.0, reach_dp, 0.0, 0.0],
2018 GlassRefraction::EdgeLens { reach_dp } => [2.0, reach_dp, 0.0, 0.0],
2019 GlassRefraction::LayeredSurface {
2020 reach_dp,
2021 return_reach_dp,
2022 return_depth_dp,
2023 } => [3.0, reach_dp, return_reach_dp, return_depth_dp],
2024 }
2025 .map(f32::to_bits)
2026 }),
2027 shadow.map(shadow_bits),
2028 edge_return_depth_dp.map(f32::to_bits),
2029 optical_projection.map(|(x, y)| [x, y].map(f32::to_bits)),
2030 activity.map(f32::to_bits),
2031 resting_tint.map(|Color(r, g, b, a)| [r, g, b, a].map(f32::to_bits)),
2032 [*highlight_boost, *saturation_boost].map(f32::to_bits),
2033 tint_alpha_multiplier.map(f32::to_bits),
2034 touch.map(|(x, y, strength)| [x, y, strength].map(f32::to_bits)),
2035 press_depth.map(f32::to_bits),
2036 touch_radius_dp.map(f32::to_bits),
2037 )
2038 };
2039 bits(a) == bits(b)
2040 && match (&a.morph, &b.morph) {
2041 (None, None) => true,
2042 (Some(a), Some(b)) => glass_morphs_match(a, b),
2043 _ => false,
2044 }
2045}
2046
2047fn shadow_bits(shadow: GlassShadow) -> [u32; 7] {
2048 [
2049 shadow.color.r(),
2050 shadow.color.g(),
2051 shadow.color.b(),
2052 shadow.color.a(),
2053 shadow.radius,
2054 shadow.offset_y,
2055 shadow.spread,
2056 ]
2057 .map(f32::to_bits)
2058}
2059
2060fn glass_morphs_match(a: &GlassMorph, b: &GlassMorph) -> bool {
2061 let shape_bits =
2062 |&(x, y, w, h, r): &(f32, f32, f32, f32, f32)| [x, y, w, h, r].map(f32::to_bits);
2063 let bits = |m: &GlassMorph| {
2064 let GlassMorph {
2065 node_size,
2066 primary,
2067 shapes: _,
2068 glue,
2069 wobble_amplitude,
2070 wobble_phase,
2071 bulge_amplitude,
2072 bulge_direction,
2073 ellipse_blend,
2074 capsule_smoothing_dp,
2075 deformation,
2076 zoom_anchor,
2077 } = m;
2078 (
2079 [
2080 node_size.0,
2081 node_size.1,
2082 *glue,
2083 *wobble_amplitude,
2084 *wobble_phase,
2085 *bulge_amplitude,
2086 *bulge_direction,
2087 *ellipse_blend,
2088 *capsule_smoothing_dp,
2089 zoom_anchor.0,
2090 zoom_anchor.1,
2091 ]
2092 .map(f32::to_bits),
2093 shape_bits(primary),
2094 deformation
2095 .map(|GlassDeformation { axis, along }| [axis.0, axis.1, along].map(f32::to_bits)),
2096 )
2097 };
2098 bits(a) == bits(b)
2099 && a.shapes
2100 .iter()
2101 .map(shape_bits)
2102 .eq(b.shapes.iter().map(shape_bits))
2103}
2104
2105fn morph_output_support(
2106 morph: &GlassMorph,
2107 activity: f32,
2108 morph_pad: f32,
2109 shadow_reach: f32,
2110 shadow_offset: f32,
2111) -> Rect {
2112 let (px, py, pw, ph, _) = morph.primary;
2113 let (along, (ax, ay)) = morph.deformation.map_or((1.0, (1.0, 0.0)), |deformation| {
2114 (
2115 1.0 + (deformation.along() - 1.0) * activity,
2116 deformation.axis(),
2117 )
2118 });
2119 let across = 1.0 / along;
2120 let smallest_strain = along.min(across);
2121 let reach = (morph_pad + shadow_reach + 4.0) / smallest_strain;
2122 let rows = [
2123 (
2124 (along * ax * ax + across * ay * ay).abs(),
2125 ((along - across) * ax * ay).abs(),
2126 ),
2127 (
2128 ((along - across) * ax * ay).abs(),
2129 (along * ay * ay + across * ax * ax).abs(),
2130 ),
2131 ];
2132 let half = (pw * 0.5 + reach, ph * 0.5 + reach);
2133 let extent_x = rows[0].0 * half.0 + rows[0].1 * half.1;
2134 let extent_y = rows[1].0 * half.0 + rows[1].1 * half.1;
2135 let (mut left, mut top) = (px - extent_x, py - extent_y);
2136 let (mut right, mut bottom) = (px + extent_x, py + extent_y);
2137 for (sx, sy, sw, sh, _) in &morph.shapes {
2138 left = left.min(sx - sw * 0.5 - reach);
2139 top = top.min(sy - sh * 0.5 - reach);
2140 right = right.max(sx + sw * 0.5 + reach);
2141 bottom = bottom.max(sy + sh * 0.5 + reach);
2142 }
2143 Rect {
2144 x: left,
2145 y: top - shadow_offset,
2146 width: right - left,
2147 height: bottom - top + 2.0 * shadow_offset,
2148 }
2149}
2150
2151fn set_morph_uniforms(shader: &mut RuntimeShader, morph: &GlassMorph, activity: f32) {
2152 let (node_w, node_h) = morph.node_size;
2153 let (cx, cy, w, h, radius) = morph.primary;
2154 shader.set_float2(0, node_w.max(1.0), node_h.max(1.0));
2155 shader.set_float2(2, cx, cy);
2156 shader.set_float2(4, w, h);
2157 shader.set_float(6, radius);
2158 let count = morph.shapes.len().min(GlassMorph::MAX_SHAPES);
2159 shader.set_float(30, count as f32);
2160 for (index, (sx, sy, sw, sh, sr)) in morph.shapes.iter().take(count).enumerate() {
2161 let base = 36 + index * 5;
2162 shader.set_float(base, *sx);
2163 shader.set_float(base + 1, *sy);
2164 shader.set_float(base + 2, *sw);
2165 shader.set_float(base + 3, *sh);
2166 shader.set_float(base + 4, *sr);
2167 }
2168 shader.set_float(31, morph.glue);
2169 shader.set_float(32, morph.wobble_amplitude * activity);
2170 shader.set_float(33, morph.wobble_phase);
2171 shader.set_float(26, morph.bulge_amplitude * activity);
2172 shader.set_float(27, morph.bulge_direction);
2173 shader.set_float(110, morph.ellipse_blend.clamp(0.0, 1.0) * activity);
2174 shader.set_float(
2175 cranpose_ui_graphics::liquid_glass::GLASS_CAPSULE_SMOOTHING_UNIFORM,
2176 if morph.capsule_smoothing_dp.is_finite() {
2177 morph.capsule_smoothing_dp.max(0.0) * activity
2178 } else {
2179 0.0
2180 },
2181 );
2182 if let Some(deformation) = morph.deformation {
2183 let axis = deformation.axis();
2184 let along = 1.0 + (deformation.along() - 1.0) * activity;
2185 shader.set_float2(106, axis.0, axis.1);
2186 shader.set_float(108, along);
2187 shader.set_float(109, 1.0 / along);
2188 } else {
2189 shader.set_float2(106, 1.0, 0.0);
2190 shader.set_float2(108, 1.0, 1.0);
2191 }
2192}
2193
2194#[cfg(test)]
2195#[path = "tests/material_tests.rs"]
2196mod tests;
2197
2198#[cfg(test)]
2199#[path = "tests/material_transparency_tests.rs"]
2200mod transparency_tests;