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_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_TINT_UNIFORM, GLASS_TOUCH_RADIUS_UNIFORM,
23 GLASS_TRANSMISSION_REFRACTION_UNIFORM, GraphicsLayer, LIQUID_GLASS_WGSL, LayerShape, Rect,
24 RenderEffect, RoundedCornerShape, RuntimeShader, TileMode, liquid_glass_runtime_effect,
25 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}
121
122impl GlassRefraction {
123 fn shader_parameters(self) -> (f32, f32) {
124 match self {
125 Self::Radial => (0.0, 0.0),
126 Self::Surface { reach_dp } => (1.0, normalized_refraction_reach(reach_dp)),
127 Self::EdgeLens { reach_dp } => (2.0, normalized_refraction_reach(reach_dp)),
128 }
129 }
130}
131
132fn normalized_refraction_reach(reach_dp: f32) -> f32 {
133 if reach_dp.is_finite() {
134 reach_dp.max(0.0)
135 } else {
136 0.0
137 }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq)]
144pub struct GlassShadow {
145 pub color: Color,
146 pub radius: f32,
147 pub offset_y: f32,
148 pub spread: f32,
149}
150
151impl GlassShadow {
152 pub fn new(color: Color, radius: f32, offset_y: f32, spread: f32) -> Self {
153 Self {
154 color,
155 radius: radius.max(0.0),
156 offset_y,
157 spread,
158 }
159 }
160
161 fn uniforms(self) -> Option<[f32; 8]> {
162 let values = [
163 self.color.r(),
164 self.color.g(),
165 self.color.b(),
166 self.color.a(),
167 self.radius,
168 self.offset_y,
169 self.spread,
170 1.0,
171 ];
172 (values.iter().all(|value| value.is_finite())
173 && self.radius >= 0.0
174 && (0.0..=1.0).contains(&self.color.a()))
175 .then_some(values)
176 }
177}
178
179#[derive(Clone, Debug, PartialEq, Default)]
182pub struct GlassDynamics {
183 pub edge_return_depth_dp: Option<f32>,
186 pub optical_projection: Option<(f32, f32)>,
190 pub activity: Option<f32>,
194 pub resting_tint: Option<Color>,
197 pub highlight_boost: f32,
199 pub saturation_boost: f32,
202 pub tint_alpha_multiplier: Option<f32>,
205 pub morph: Option<GlassMorph>,
208 pub touch: Option<(f32, f32, f32)>,
212 pub touch_radius_dp: Option<f32>,
214 pub press_depth: Option<f32>,
220}
221
222impl GlassDynamics {
223 fn projection(&self) -> (f32, f32) {
224 self.optical_projection
225 .filter(|(x, y)| x.is_finite() && y.is_finite() && *x > 0.0 && *y > 0.0)
226 .unwrap_or((1.0, 1.0))
227 }
228
229 pub fn touched_up(
236 mut self,
237 press: f32,
238 finger: Option<(f32, f32)>,
239 center: (f32, f32),
240 ) -> Self {
241 let press = press.clamp(0.0, 1.0);
242 if press <= 0.0 {
243 return self;
244 }
245 self.highlight_boost += 0.85 * press;
246 self.saturation_boost += 0.45 * press;
247 let (fx, fy) = finger.unwrap_or(center);
248 let carried = self.touch.map_or(0.0, |(_, _, i)| i);
249 self.touch = Some((fx, fy, (carried + press).clamp(0.0, 1.0)));
250 self
251 }
252}
253
254fn foreground_is_dark(foreground: Color) -> bool {
255 let foreground_luma =
256 0.2126 * foreground.r() + 0.7152 * foreground.g() + 0.0722 * foreground.b();
257 foreground_luma < 0.5
258}
259
260fn boost_tint_saturation(tint: Color, boost: f32) -> Color {
261 if boost.abs() <= f32::EPSILON {
262 return tint;
263 }
264 let luma = 0.2126 * tint.r() + 0.7152 * tint.g() + 0.0722 * tint.b();
265 let saturation = (1.0 + boost).max(0.0);
266 Color::rgba(
267 (luma + (tint.r() - luma) * saturation).clamp(0.0, 1.0),
268 (luma + (tint.g() - luma) * saturation).clamp(0.0, 1.0),
269 (luma + (tint.b() - luma) * saturation).clamp(0.0, 1.0),
270 tint.a(),
271 )
272}
273
274pub(crate) fn neutral_surface_tint(foreground: Color, light_alpha: f32, dark_alpha: f32) -> Color {
275 if foreground_is_dark(foreground) {
276 Color::BLACK.with_alpha(light_alpha.clamp(0.0, 1.0))
277 } else {
278 Color::WHITE.with_alpha(dark_alpha.clamp(0.0, 1.0))
279 }
280}
281
282#[derive(Clone, Debug, PartialEq, Default)]
293pub struct GlassMorph {
294 pub node_size: (f32, f32),
300 pub primary: (f32, f32, f32, f32, f32),
301 pub shapes: Vec<(f32, f32, f32, f32, f32)>,
303 pub glue: f32,
305 pub wobble_amplitude: f32,
307 pub wobble_phase: f32,
308 pub bulge_amplitude: f32,
312 pub bulge_direction: f32,
313 pub ellipse_blend: f32,
317 pub capsule_smoothing_dp: f32,
322 pub deformation: Option<GlassDeformation>,
326 pub zoom_anchor: (f32, f32),
332}
333
334#[derive(Clone, Copy, Debug, PartialEq)]
338pub struct GlassDeformation {
339 axis: (f32, f32),
340 along: f32,
341}
342
343impl GlassDeformation {
344 pub fn incompressible(axis: (f32, f32), along: f32) -> Self {
345 let length = (axis.0 * axis.0 + axis.1 * axis.1).sqrt();
346 let axis = if length > f32::EPSILON {
347 (axis.0 / length, axis.1 / length)
348 } else {
349 (1.0, 0.0)
350 };
351 Self {
352 axis,
353 along: along.max(f32::EPSILON),
354 }
355 }
356
357 pub fn axis(self) -> (f32, f32) {
358 self.axis
359 }
360
361 pub fn along(self) -> f32 {
362 self.along
363 }
364
365 pub fn across(self) -> f32 {
366 1.0 / self.along
367 }
368}
369
370impl GlassMorph {
371 pub const MAX_SHAPES: usize = 8;
373}
374
375#[derive(Clone, Copy, Debug, PartialEq)]
378pub struct GlassKeyFill {
379 pub height_dp: f32,
381 pub curvature: f32,
383 pub angle_radians: f32,
385 pub saturation: f32,
387 pub luma_gain: f32,
389 pub offset: f32,
391 pub scale_with_surface: bool,
393}
394
395impl GlassKeyFill {
396 fn uniforms(self) -> Option<[f32; 7]> {
397 let values = [
398 self.height_dp,
399 self.curvature,
400 self.angle_radians,
401 self.saturation,
402 self.luma_gain,
403 self.offset,
404 f32::from(self.scale_with_surface),
405 ];
406 (values.iter().all(|value| value.is_finite())
407 && self.height_dp > 0.0
408 && self.curvature > 0.0)
409 .then_some(values)
410 }
411}
412
413#[derive(Clone, Copy, Debug, PartialEq)]
416pub struct GlassSpectrum {
417 pub angle_radians: f32,
420 pub step_dp: f32,
422 pub vertical_scale: f32,
424 pub opacity_near: f32,
426 pub opacity_far: f32,
428 pub fade_depth_dp: f32,
430 pub extent_dp: f32,
432}
433
434impl GlassSpectrum {
435 fn uniforms(self) -> Option<[f32; 8]> {
436 let values = [
437 self.angle_radians,
438 self.step_dp,
439 self.vertical_scale,
440 self.opacity_near,
441 self.opacity_far,
442 self.fade_depth_dp,
443 self.extent_dp,
444 1.0,
445 ];
446 (values.iter().all(|value| value.is_finite())
447 && self.vertical_scale >= 0.0
448 && (0.0..=1.0).contains(&self.opacity_near)
449 && (0.0..=1.0).contains(&self.opacity_far)
450 && self.fade_depth_dp >= 0.0
451 && self.extent_dp >= 0.0)
452 .then_some(values)
453 }
454
455 fn capture_reach(self) -> f32 {
456 self.step_dp.abs() * 3.0 * self.vertical_scale.max(1.0)
457 }
458}
459
460#[derive(Clone, Copy, Debug, PartialEq)]
464pub struct GlassFaceResponse {
465 pub gain: f32,
467 pub start_dp: f32,
469 pub end_dp: f32,
472 pub illumination: f32,
474}
475
476impl GlassFaceResponse {
477 fn uniforms(self) -> Option<[f32; 4]> {
478 let values = [
479 1.0 - self.gain,
480 self.start_dp,
481 self.end_dp,
482 self.illumination,
483 ];
484 (values.iter().all(|value| value.is_finite())
485 && (0.0..=1.0).contains(&self.gain)
486 && self.start_dp >= 0.0
487 && self.end_dp >= self.start_dp)
488 .then_some(values)
489 }
490}
491
492#[derive(Clone, Debug, PartialEq)]
495pub struct Glass {
496 pub variant: GlassVariant,
497 pub shape: LiquidShape,
498 pub tint: Option<Color>,
500 pub tint_amount: Option<GlassTintAmount>,
502 pub blur_radius: Option<f32>,
504 pub backdrop_blur: Option<(f32, f32)>,
506 pub saturation: Option<f32>,
508 pub refraction_depth: f32,
512 pub refraction_depth_dp: Option<f32>,
516 pub refraction: GlassRefraction,
518 pub refraction_curve: f32,
520 pub dispersion: f32,
522 pub edge_spectrum: Option<GlassSpectrum>,
524 pub transmission_refraction: f32,
528 pub meniscus_absorption: f32,
531 pub fold_depth: f32,
535 pub optical_zoom: f32,
538 pub rim_reflection: f32,
541 pub ink_recolor: Option<(Color, f32)>,
545 pub highlight: f32,
547 pub face_lighting: bool,
549 pub key_fill: Option<GlassKeyFill>,
551 pub face_response: Option<GlassFaceResponse>,
553 pub inner_shadow: Option<GlassShadow>,
555 pub lift: Option<f32>,
558 pub contrast: Option<f32>,
563 pub shadow: bool,
565 pub shadow_style: Option<GlassShadow>,
567 pub clip: bool,
570 pub foreground: Option<Color>,
573 pub adaptive_frost: f32,
575 pub adaptive_tone: bool,
578}
579
580impl Glass {
581 pub fn regular() -> Self {
583 Self {
584 variant: GlassVariant::Regular,
585 shape: LiquidShape::Capsule,
586 tint: None,
587 tint_amount: None,
588 blur_radius: None,
589 backdrop_blur: None,
590 saturation: None,
591 refraction_depth: 0.0,
592 refraction_depth_dp: None,
593 refraction: GlassRefraction::Radial,
594 refraction_curve: 0.25,
595 dispersion: 0.0,
596 edge_spectrum: None,
597 transmission_refraction: 0.0,
598 meniscus_absorption: 1.0,
599 fold_depth: 0.0,
600 optical_zoom: 1.0,
601 rim_reflection: 1.0,
602 ink_recolor: None,
603 highlight: 0.9,
604 face_lighting: true,
605 key_fill: None,
606 face_response: None,
607 inner_shadow: None,
608 lift: None,
609 contrast: None,
610 shadow: true,
611 shadow_style: None,
612 clip: true,
613 foreground: None,
614 adaptive_frost: 0.65,
615 adaptive_tone: false,
616 }
617 }
618
619 pub fn clear() -> Self {
620 Self {
621 variant: GlassVariant::Clear,
622 ..Self::regular()
623 }
624 }
625
626 pub fn lens() -> Self {
629 Self {
630 variant: GlassVariant::Lens,
631 shape: LiquidShape::Capsule,
632 tint: Some(Color::rgba(1.0, 1.0, 1.0, 0.07)),
633 tint_amount: None,
634 blur_radius: None,
635 backdrop_blur: None,
636 saturation: None,
637 refraction_depth: 0.34,
638 refraction_depth_dp: None,
639 refraction: GlassRefraction::Radial,
640 refraction_curve: 1.0,
641 dispersion: 0.30,
642 edge_spectrum: None,
643 transmission_refraction: 1.0,
644 meniscus_absorption: 1.0,
645 fold_depth: 0.0,
646 optical_zoom: 1.0,
647 rim_reflection: 1.0,
648 ink_recolor: None,
649 highlight: 1.15,
650 face_lighting: true,
651 key_fill: None,
652 face_response: None,
653 inner_shadow: None,
654 lift: None,
655 contrast: None,
656 shadow: true,
657 shadow_style: None,
658 clip: true,
659 foreground: None,
660 adaptive_frost: 0.0,
661 adaptive_tone: false,
662 }
663 }
664
665 pub fn face_lighting(mut self, enabled: bool) -> Self {
667 self.face_lighting = enabled;
668 self
669 }
670
671 pub fn key_fill(mut self, lighting: GlassKeyFill) -> Self {
674 self.key_fill = Some(lighting);
675 self
676 }
677
678 pub fn face_response(mut self, response: GlassFaceResponse) -> Self {
680 self.face_response = Some(response);
681 self
682 }
683
684 pub fn inner_shadow(mut self, shadow: GlassShadow) -> Self {
688 self.inner_shadow = shadow.uniforms().map(|_| shadow);
689 self
690 }
691
692 pub fn shape(mut self, shape: LiquidShape) -> Self {
693 self.shape = shape;
694 self
695 }
696
697 pub fn tint(mut self, tint: Color) -> Self {
698 self.tint = Some(tint);
699 self
700 }
701
702 pub fn blur_radius(mut self, radius_dp: f32) -> Self {
703 self.blur_radius = Some(radius_dp);
704 self
705 }
706
707 pub fn saturation(mut self, saturation: f32) -> Self {
708 self.saturation = Some(saturation);
709 self
710 }
711
712 pub fn refraction_depth(mut self, fraction: f32) -> Self {
713 self.refraction_depth = fraction.clamp(0.0, 2.0);
714 self.refraction_depth_dp = None;
715 self
716 }
717
718 pub fn refraction_depth_dp(mut self, depth_dp: f32) -> Self {
721 self.refraction_depth_dp = Some(depth_dp.max(0.0));
722 self
723 }
724
725 pub fn surface_refraction(mut self, reach_dp: f32) -> Self {
730 self.refraction = GlassRefraction::Surface {
731 reach_dp: normalized_refraction_reach(reach_dp),
732 };
733 self
734 }
735
736 pub fn edge_spectrum(mut self, spectrum: GlassSpectrum) -> Self {
739 self.edge_spectrum = spectrum.uniforms().map(|_| spectrum);
740 self
741 }
742
743 pub fn edge_refraction(mut self, reach_dp: f32) -> Self {
747 self.refraction = GlassRefraction::EdgeLens {
748 reach_dp: normalized_refraction_reach(reach_dp),
749 };
750 self
751 }
752
753 pub fn refraction_curve(mut self, curve: f32) -> Self {
754 self.refraction_curve = curve.clamp(0.05, 1.0);
755 self
756 }
757
758 pub fn dispersion(mut self, strength: f32) -> Self {
759 self.dispersion = strength.clamp(0.0, 1.0);
760 self
761 }
762
763 pub fn transmission_refraction(mut self, strength: f32) -> Self {
764 self.transmission_refraction = strength.clamp(0.0, 1.0);
765 self
766 }
767
768 pub fn meniscus_absorption(mut self, strength: f32) -> Self {
769 self.meniscus_absorption = strength.clamp(0.0, 1.0);
770 self
771 }
772
773 pub fn fold_depth(mut self, depth_dp: f32) -> Self {
775 self.fold_depth = depth_dp.max(0.0);
776 self
777 }
778
779 pub fn optical_zoom(mut self, zoom: f32) -> Self {
781 self.optical_zoom = zoom.max(1.0);
782 self
783 }
784
785 pub fn rim_reflection(mut self, reflectivity: f32) -> Self {
787 self.rim_reflection = reflectivity.clamp(0.0, 2.0);
788 self
789 }
790
791 pub fn ink_recolor(mut self, color: Color, strength: f32) -> Self {
794 self.ink_recolor = Some((color, strength.clamp(0.0, 1.0)));
795 self
796 }
797
798 pub fn highlight(mut self, highlight: f32) -> Self {
799 self.highlight = highlight;
800 self
801 }
802
803 pub fn lift(mut self, lift: f32) -> Self {
806 self.lift = Some(lift);
807 self
808 }
809
810 pub fn contrast(mut self, contrast: f32) -> Self {
813 self.contrast = Some(contrast.max(0.05));
814 self
815 }
816
817 pub fn adaptive_frost(mut self, foreground: Color, strength: f32) -> Self {
818 self.foreground = Some(foreground);
819 self.adaptive_frost = strength.clamp(0.0, 1.0);
820 self
821 }
822
823 pub fn adaptive_tone(mut self, foreground: Color) -> Self {
829 self.foreground = Some(foreground);
830 self.adaptive_tone = true;
831 self.adaptive_frost = 0.0;
832 self
833 }
834
835 pub fn shadow(mut self, shadow: bool) -> Self {
836 self.shadow = shadow;
837 self
838 }
839
840 pub fn shadow_style(mut self, shadow: GlassShadow) -> Self {
841 self.shadow_style = Some(shadow);
842 self
843 }
844
845 pub fn no_clip(mut self) -> Self {
848 self.clip = false;
849 self
850 }
851
852 pub fn tint_amount(mut self, amount: GlassTintAmount) -> Self {
855 self.tint_amount = Some(amount);
856 self
857 }
858
859 pub fn backdrop_blur(mut self, radius_dp: f32, opacity: f32) -> Self {
863 self.backdrop_blur = (radius_dp.is_finite() && opacity.is_finite())
864 .then_some((radius_dp.max(0.0), opacity.clamp(0.0, 1.0)));
865 self
866 }
867
868 fn default_blur_radius(&self) -> f32 {
869 match self.variant {
870 GlassVariant::Regular => 8.0,
871 GlassVariant::Clear => 3.0,
872 GlassVariant::Lens => 0.0,
873 }
874 }
875
876 fn default_saturation(&self) -> f32 {
877 match self.variant {
878 GlassVariant::Regular => 1.5,
879 GlassVariant::Clear => 1.25,
880 GlassVariant::Lens => 1.0,
881 }
882 }
883
884 pub fn backdrop_effect(
888 &self,
889 colors: &LiquidColors,
890 density: f32,
891 dynamics: GlassDynamics,
892 ) -> RenderEffect {
893 self.resolve(colors).backdrop_effect(density, dynamics)
894 }
895
896 fn preferred_foreground(&self, colors: &LiquidColors) -> Color {
897 self.foreground
898 .filter(|color| {
899 [color.r(), color.g(), color.b(), color.a()]
900 .iter()
901 .all(|value| value.is_finite())
902 })
903 .unwrap_or(colors.label)
904 }
905
906 pub(crate) fn resolve(&self, colors: &LiquidColors) -> ResolvedGlass {
907 let lift = self.lift.unwrap_or(match (self.variant, colors.is_dark) {
908 (GlassVariant::Regular, false) => 0.42,
909 (GlassVariant::Regular, true) => -0.38,
910 (GlassVariant::Clear, false) => 0.12,
911 (GlassVariant::Clear, true) => -0.12,
912 (GlassVariant::Lens, false) => 0.10,
913 (GlassVariant::Lens, true) => -0.08,
914 });
915 let foreground = self.preferred_foreground(colors);
916 let shadow = self.shadow_style.unwrap_or_else(|| {
917 GlassShadow::new(
918 Color::BLACK.with_alpha(match (self.variant, colors.is_dark) {
919 (GlassVariant::Lens, false) => 0.14,
920 (GlassVariant::Lens, true) => 0.28,
921 (_, false) => 0.16,
922 (_, true) => 0.5,
923 }),
924 if self.variant == GlassVariant::Lens {
925 10.0
926 } else {
927 22.0
928 },
929 if self.variant == GlassVariant::Lens {
930 3.0
931 } else {
932 8.0
933 },
934 if self.variant == GlassVariant::Lens {
935 -6.0
936 } else {
937 -2.0
938 },
939 )
940 });
941 ResolvedGlass {
942 shape: self.shape,
943 tint: self.tint.unwrap_or(colors.glass_tint),
944 tint_amount: self.tint_amount,
945 backdrop_blur: self.backdrop_blur,
946 blur_radius_dp: self
947 .blur_radius
948 .unwrap_or_else(|| self.default_blur_radius()),
949 saturation: self.saturation.unwrap_or_else(|| self.default_saturation()),
950 refraction_depth: self.refraction_depth,
951 refraction_depth_dp: self.refraction_depth_dp,
952 refraction: self.refraction,
953 refraction_curve: self.refraction_curve,
954 dispersion: self.dispersion,
955 edge_spectrum: self.edge_spectrum,
956 transmission_refraction: self.transmission_refraction,
957 meniscus_absorption: self.meniscus_absorption,
958 fold_depth: self.fold_depth,
959 optical_zoom: self.optical_zoom,
960 rim_reflection: self.rim_reflection,
961 ink_recolor: self.ink_recolor,
962 highlight: self.highlight,
963 face_lighting: self.face_lighting,
964 key_fill: self.key_fill,
965 face_response: self.face_response,
966 inner_shadow: self.inner_shadow,
967 lift,
968 contrast: self.contrast.unwrap_or(match self.variant {
969 GlassVariant::Lens => 1.0,
970 _ => 1.03,
971 }),
972 shadow: self.shadow,
973 clip: self.clip,
974 foreground_luma: 0.2126 * foreground.r()
975 + 0.7152 * foreground.g()
976 + 0.0722 * foreground.b(),
977 adaptive_frost: self.adaptive_frost,
978 adaptive_tone: self.adaptive_tone,
979 rim_style: if self.variant == GlassVariant::Lens {
980 1.0
981 } else {
982 0.0
983 },
984 shadow_color: shadow.color,
985 shadow_radius: shadow.radius,
986 shadow_offset_y: shadow.offset_y,
987 shadow_spread: shadow.spread,
988 }
989 }
990}
991
992impl Default for Glass {
993 fn default() -> Self {
994 Self::regular()
995 }
996}
997
998#[derive(Clone, Debug, PartialEq)]
999pub(crate) struct ResolvedGlass {
1000 pub shape: LiquidShape,
1001 pub tint: Color,
1002 tint_amount: Option<GlassTintAmount>,
1003 backdrop_blur: Option<(f32, f32)>,
1004 pub blur_radius_dp: f32,
1005 pub saturation: f32,
1006 pub refraction_depth: f32,
1007 pub refraction_depth_dp: Option<f32>,
1008 pub refraction: GlassRefraction,
1009 pub refraction_curve: f32,
1010 pub dispersion: f32,
1011 pub edge_spectrum: Option<GlassSpectrum>,
1012 pub transmission_refraction: f32,
1013 pub meniscus_absorption: f32,
1014 pub fold_depth: f32,
1015 pub optical_zoom: f32,
1016 pub rim_reflection: f32,
1017 pub ink_recolor: Option<(Color, f32)>,
1018 pub highlight: f32,
1019 pub face_lighting: bool,
1020 pub key_fill: Option<GlassKeyFill>,
1021 pub face_response: Option<GlassFaceResponse>,
1023 pub lift: f32,
1024 pub inner_shadow: Option<GlassShadow>,
1026 pub contrast: f32,
1027 pub shadow: bool,
1028 pub clip: bool,
1029 pub rim_style: f32,
1030 pub foreground_luma: f32,
1031 pub adaptive_frost: f32,
1032 pub adaptive_tone: bool,
1033 pub shadow_color: Color,
1034 pub shadow_radius: f32,
1035 pub shadow_offset_y: f32,
1036 pub shadow_spread: f32,
1037}
1038
1039fn set_edge_return_depth(shader: &mut RuntimeShader, depth: Option<f32>) {
1040 if let Some(depth) = depth.filter(|depth| depth.is_finite() && *depth >= 0.0) {
1041 shader.set_float2(GLASS_EDGE_RETURN_DEPTH_UNIFORM, depth, 1.0);
1042 }
1043}
1044
1045impl ResolvedGlass {
1046 pub(crate) fn without_transparency(mut self, colors: &LiquidColors) -> Self {
1050 self.tint = colors.surface;
1051 self.tint_amount = None;
1052 self.backdrop_blur = None;
1053 self.blur_radius_dp = 0.0;
1054 self.saturation = 1.0;
1055 self.refraction_depth = 0.0;
1056 self.refraction_depth_dp = None;
1057 self.dispersion = 0.0;
1058 self.edge_spectrum = None;
1059 self.transmission_refraction = 0.0;
1060 self.meniscus_absorption = 0.0;
1061 self.rim_reflection = 0.0;
1062 self.adaptive_frost = 0.0;
1063 self
1064 }
1065
1066 fn clips_in_shader(&self) -> bool {
1067 self.key_fill.and_then(GlassKeyFill::uniforms).is_some()
1068 }
1069 pub(crate) fn backdrop_effect(&self, density: f32, dynamics: GlassDynamics) -> RenderEffect {
1070 self.runtime_effect(density, &dynamics, false)
1071 }
1072
1073 fn blur_radii(&self, density: f32, activity: f32, content_mask: bool) -> (f32, f32) {
1074 if content_mask || self.tint_amount.is_some() {
1075 return (0.0, 0.0);
1076 }
1077 let radius = self.blur_radius_dp * density * activity;
1078 if radius > WCKSRD_OPTICAL_BLUR_RADIUS_PX {
1079 (0.0, radius)
1080 } else {
1081 (radius, 0.0)
1082 }
1083 }
1084
1085 fn runtime_effect(
1086 &self,
1087 density: f32,
1088 dynamics: &GlassDynamics,
1089 content_mask: bool,
1090 ) -> RenderEffect {
1091 self.runtime_effect_with_content(density, dynamics, content_mask, false)
1092 }
1093
1094 fn runtime_effect_with_content(
1095 &self,
1096 density: f32,
1097 dynamics: &GlassDynamics,
1098 content_mask: bool,
1099 separate_content: bool,
1100 ) -> RenderEffect {
1101 let density = density.max(f32::EPSILON);
1102 let activity = dynamics
1103 .activity
1104 .filter(|value| value.is_finite())
1105 .unwrap_or(1.0)
1106 .clamp(0.0, 1.0);
1107 static SHADER: OnceLock<RuntimeShader> = OnceLock::new();
1108 let mut shader = SHADER
1109 .get_or_init(|| {
1110 let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
1111 shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
1112 shader.set_float2(GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM, 0.0, 0.0);
1113 specialize_liquid_glass(&mut shader);
1114 shader
1115 })
1116 .clone();
1117 if let Some(morph) = dynamics.morph.as_ref() {
1118 set_morph_uniforms(&mut shader, morph, activity);
1119 } else {
1120 shader.set_float2(0, 0.0, 0.0);
1121 shader.set_float(6, self.shape.shader_radius_px(density));
1122 }
1123 let projection = dynamics.projection();
1124 shader.set_float2(GLASS_OPTICAL_PROJECTION_UNIFORM, projection.0, projection.1);
1125 let press_depth = dynamics.press_depth.unwrap_or(1.0).clamp(0.0, 1.0);
1126 shader.set_float(9, self.refraction_depth * activity * press_depth);
1127 let (refraction_mode, refraction_reach) = self.refraction.shader_parameters();
1128 shader.set_float(GLASS_REFRACTION_MODE_UNIFORM, refraction_mode);
1129 set_edge_return_depth(&mut shader, dynamics.edge_return_depth_dp);
1130 shader.set_float(
1131 GLASS_EDGE_REFRACTION_REACH_UNIFORM,
1132 refraction_reach * activity * press_depth,
1133 );
1134 shader.set_float(
1135 GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM,
1136 self.refraction_depth_dp.unwrap_or(0.0) * activity * press_depth,
1137 );
1138 shader.set_float(
1139 GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM,
1140 if self.refraction_depth_dp.is_some() {
1141 1.0
1142 } else {
1143 0.0
1144 },
1145 );
1146 shader.set_float(
1147 GLASS_REFRACTION_CURVE_UNIFORM,
1148 self.refraction_curve * activity,
1149 );
1150 shader.set_float(
1151 GLASS_DISPERSION_UNIFORM,
1152 self.dispersion * activity * press_depth,
1153 );
1154 let spectrum_reach = self.set_spectrum_uniforms(&mut shader);
1155 shader.set_float(
1156 GLASS_TRANSMISSION_REFRACTION_UNIFORM,
1157 self.transmission_refraction * activity,
1158 );
1159 shader.set_float(
1160 GLASS_MENISCUS_ABSORPTION_UNIFORM,
1161 self.meniscus_absorption * press_depth,
1162 );
1163 shader.set_float(
1164 GLASS_FOLD_DEPTH_UNIFORM,
1165 self.fold_depth.max(0.0) * press_depth,
1166 );
1167 shader.set_float(
1168 GLASS_OPTICAL_ZOOM_UNIFORM,
1169 1.0 + (self.optical_zoom - 1.0).max(0.0) * activity,
1170 );
1171 let zoom_anchor = dynamics
1172 .morph
1173 .as_ref()
1174 .map_or((0.0, 0.0), |morph| morph.zoom_anchor);
1175 shader.set_float2(
1176 GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
1177 zoom_anchor.0,
1178 zoom_anchor.1,
1179 );
1180 shader.set_float(121, self.rim_reflection.max(0.001));
1181 let (ink_color, ink_strength) = self
1182 .ink_recolor
1183 .map_or((Color::TRANSPARENT, 0.0), |(color, strength)| {
1184 (color, strength * activity)
1185 });
1186 shader.set_float(124, ink_color.r());
1187 shader.set_float(125, ink_color.g());
1188 shader.set_float(126, ink_color.b());
1189 shader.set_float(127, ink_strength);
1190 let (light_x, light_y) = glass_light_direction();
1191 shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM, light_x);
1192 shader.set_float(GLASS_LIGHT_DIRECTION_UNIFORM + 1, light_y);
1193 self.set_lighting_uniforms(&mut shader, dynamics);
1194 shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, density);
1195 shader.set_float(
1196 11,
1197 (self.highlight + dynamics.highlight_boost).clamp(0.0, 2.0) * activity,
1198 );
1199 let dynamic_tint = boost_tint_saturation(self.tint, dynamics.saturation_boost);
1200 let dynamic_tint_alpha = (dynamic_tint.a()
1201 * dynamics
1202 .tint_alpha_multiplier
1203 .unwrap_or(1.0)
1204 .clamp(0.0, 2.0)
1205 * activity)
1206 .clamp(0.0, 1.0);
1207 shader.set_float4(
1208 14,
1209 dynamic_tint.r(),
1210 dynamic_tint.g(),
1211 dynamic_tint.b(),
1212 dynamic_tint_alpha,
1213 );
1214 let saturation = (self.saturation + dynamics.saturation_boost).max(0.0);
1215 shader.set_float(18, 1.0 + (saturation - 1.0) * activity);
1216 shader.set_float(20, self.lift * activity);
1217 shader.set_float(21, 0.5 * activity);
1218 shader.set_float2(22, 0.0, 1.0);
1219 shader.set_float(24, 1.0 + (self.contrast - 1.0) * activity);
1220 shader.set_float(28, self.rim_style * activity);
1221 let (wcksrd_blur_radius, gaussian_blur_radius) =
1222 self.blur_radii(density, activity, content_mask);
1223 shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, wcksrd_blur_radius);
1224 shader.set_float(GLASS_ACTIVITY_UNIFORM, activity);
1225 let resting_tint = dynamics.resting_tint.unwrap_or(Color::TRANSPARENT);
1226 shader.set_float4(
1227 GLASS_RESTING_TINT_UNIFORM,
1228 resting_tint.r(),
1229 resting_tint.g(),
1230 resting_tint.b(),
1231 resting_tint.a(),
1232 );
1233 shader.set_float(112, if content_mask { 1.0 } else { 0.0 });
1234 shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, self.adaptive_frost * activity);
1235 shader.set_float(97, self.foreground_luma);
1236 shader.set_float(GLASS_ADAPTIVE_TONE_UNIFORM, f32::from(self.adaptive_tone));
1237 self.set_backdrop_uniforms(&mut shader, density, content_mask);
1238 shader.set_float(
1239 cranpose_ui_graphics::GLASS_FOREGROUND_CONTENT_UNIFORM,
1240 f32::from(separate_content),
1241 );
1242 let dynamic_shadow = !self.clip && self.shadow;
1243 shader.set_float(
1244 102,
1245 if dynamic_shadow {
1246 self.shadow_color.a() * 0.55 * activity
1247 } else {
1248 0.0
1249 },
1250 );
1251 shader.set_float(103, self.shadow_radius);
1252 shader.set_float(104, self.shadow_offset_y);
1253 shader.set_float(105, self.shadow_spread);
1254 let morph_pad = dynamics.morph.as_ref().map_or(0.0, |morph| {
1255 let (px, py, pw, ph, _) = morph.primary;
1256 let (left, top) = (px - pw * 0.5, py - ph * 0.5);
1257 let (right, bottom) = (px + pw * 0.5, py + ph * 0.5);
1258 let mut shape_reach = 0.0f32;
1259 for (sx, sy, sw, sh, _) in &morph.shapes {
1260 let reach_x = ((sx + sw * 0.5) - right)
1261 .max(left - (sx - sw * 0.5))
1262 .max(0.0);
1263 let reach_y = ((sy + sh * 0.5) - bottom)
1264 .max(top - (sy - sh * 0.5))
1265 .max(0.0);
1266 shape_reach = shape_reach.max(reach_x.max(reach_y));
1267 }
1268 let glue_pad = if morph.shapes.is_empty() {
1269 0.0
1270 } else {
1271 morph.glue * 2.0
1272 };
1273 morph.wobble_amplitude * 2.0 + morph.bulge_amplitude + shape_reach + glue_pad
1274 });
1275 shader.set_input_padding(
1276 (self.input_padding()
1277 + if refraction_mode >= 1.5 {
1278 refraction_reach * (26.5 / 9.0) + spectrum_reach
1279 } else {
1280 refraction_reach
1281 }
1282 + morph_pad
1283 + wcksrd_blur_radius / density)
1284 * projection.0.max(projection.1).max(1.0),
1285 );
1286 if let Some(morph) = dynamics.morph.as_ref() {
1287 let shadow_reach = if dynamic_shadow {
1288 self.shadow_radius + self.shadow_offset_y.abs() + self.shadow_spread.max(0.0)
1289 } else {
1290 0.0
1291 };
1292 let support = morph_output_support(
1293 morph,
1294 activity,
1295 morph_pad,
1296 shadow_reach,
1297 if dynamic_shadow {
1298 self.shadow_offset_y.abs()
1299 } else {
1300 0.0
1301 },
1302 );
1303 let (cx, cy, _, _, _) = morph.primary;
1304 let support = Rect {
1305 x: cx + (support.x - cx) * projection.0,
1306 y: cy + (support.y - cy) * projection.1,
1307 width: support.width * projection.0,
1308 height: support.height * projection.1,
1309 };
1310 let (node_w, node_h) = morph.node_size;
1311 let overhang = (-support.x)
1312 .max(-support.y)
1313 .max(support.x + support.width - node_w)
1314 .max(support.y + support.height - node_h)
1315 .max(0.0);
1316 shader.set_output_padding((morph_pad + shadow_reach + 4.0).max(overhang));
1317 shader.set_output_support(Some(support));
1318 }
1319
1320 let optical_effect = liquid_glass_runtime_effect(shader);
1321 if gaussian_blur_radius > f32::EPSILON {
1322 RenderEffect::blur_with_edge_treatment(gaussian_blur_radius, TileMode::Mirror)
1323 .then(optical_effect)
1324 } else {
1325 optical_effect
1326 }
1327 }
1328
1329 fn set_backdrop_uniforms(&self, shader: &mut RuntimeShader, density: f32, content_mask: bool) {
1330 if let Some(amount) = self.tint_amount {
1331 shader.set_float(
1332 cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM,
1333 8.0 * density,
1334 );
1335 shader.set_float(
1336 cranpose_ui_graphics::GLASS_PANE_BLEND_UNIFORM + 1,
1337 amount.percent() / 100.0,
1338 );
1339 }
1340 if !content_mask && let Some((radius, opacity)) = self.backdrop_blur {
1341 shader.set_float2(
1342 cranpose_ui_graphics::GLASS_BACKDROP_BLUR_UNIFORM,
1343 radius * density,
1344 opacity,
1345 );
1346 }
1347 }
1348
1349 fn set_spectrum_uniforms(&self, shader: &mut RuntimeShader) -> f32 {
1350 let spectrum = self
1351 .edge_spectrum
1352 .and_then(|spectrum| spectrum.uniforms().map(|values| (spectrum, values)));
1353 if let Some((spectrum, values)) = spectrum {
1354 for (index, value) in values.into_iter().enumerate() {
1355 shader.set_float(GLASS_EDGE_SPECTRUM_UNIFORM + index, value);
1356 }
1357 spectrum.capture_reach()
1358 } else {
1359 40.787 * self.dispersion
1360 }
1361 }
1362
1363 fn set_lighting_uniforms(&self, shader: &mut RuntimeShader, dynamics: &GlassDynamics) {
1364 let (touch_x, touch_y, touch_intensity) = dynamics.touch.unwrap_or((0.0, 0.0, 0.0));
1365 shader.set_float(118, touch_x);
1366 shader.set_float(119, touch_y);
1367 shader.set_float(120, touch_intensity.clamp(0.0, 1.0));
1368 shader.set_float(
1369 GLASS_FACE_LIGHTING_OFF_UNIFORM,
1370 f32::from(!self.face_lighting),
1371 );
1372 shader.set_float(
1373 GLASS_TOUCH_RADIUS_UNIFORM,
1374 dynamics
1375 .touch_radius_dp
1376 .filter(|radius| radius.is_finite() && *radius > 0.0)
1377 .unwrap_or(58.0),
1378 );
1379 shader.set_float(
1380 GLASS_LAYER_CLIPPED_UNIFORM,
1381 f32::from(self.clip && !self.clips_in_shader()),
1382 );
1383 if let Some(values) = self.key_fill.and_then(GlassKeyFill::uniforms) {
1384 for (index, value) in values.into_iter().enumerate() {
1385 shader.set_float(GLASS_KEY_FILL_UNIFORM + index, value);
1386 }
1387 }
1388 if let Some(values) = self.face_response.and_then(GlassFaceResponse::uniforms) {
1389 for (index, value) in values.into_iter().enumerate() {
1390 shader.set_float(GLASS_FACE_RESPONSE_UNIFORM + index, value);
1391 }
1392 }
1393 if let Some(values) = self.inner_shadow.and_then(GlassShadow::uniforms) {
1394 for (index, value) in values.into_iter().enumerate() {
1395 shader.set_float(GLASS_INNER_SHADOW_UNIFORM + index, value);
1396 }
1397 }
1398 }
1399
1400 fn input_padding(&self) -> f32 {
1401 2.0
1402 }
1403}
1404
1405pub trait LiquidModifierExt {
1407 fn glass_effect(self, glass: Glass) -> Modifier;
1413
1414 fn glass_effect_with(
1418 self,
1419 glass: Glass,
1420 dynamics: impl Fn() -> GlassDynamics + 'static,
1421 ) -> Modifier;
1422}
1423
1424impl LiquidModifierExt for Modifier {
1425 fn glass_effect(self, glass: Glass) -> Modifier {
1426 self.glass_effect_with(glass, GlassDynamics::default)
1427 }
1428
1429 fn glass_effect_with(
1430 self,
1431 glass: Glass,
1432 dynamics: impl Fn() -> GlassDynamics + 'static,
1433 ) -> Modifier {
1434 let colors = crate::theme::liquid_colors();
1435 let options = cranpose_services::local_accessibility_options().current();
1436 let resolved = if options.reduce_transparency {
1437 glass.resolve(&colors).without_transparency(&colors)
1438 } else {
1439 glass.resolve(&colors)
1440 };
1441 let shape = resolved.shape;
1442
1443 let mut modifier = self;
1444 if resolved.shadow && resolved.clip {
1445 let shadow_color = resolved.shadow_color;
1446 let (radius, offset_y, spread) = (
1447 resolved.shadow_radius,
1448 resolved.shadow_offset_y,
1449 resolved.shadow_spread,
1450 );
1451 modifier = modifier.drop_shadow(shape.layer_shape(), move |scope| {
1452 scope.radius = radius;
1453 scope.spread = spread;
1454 scope.offset.y = offset_y;
1455 scope.color = shadow_color;
1456 scope.cutout = true;
1457 });
1458 }
1459
1460 let layer = cached_glass_layer(resolved);
1461 modifier.graphics_layer(move || layer(current_density(), dynamics()))
1462 }
1463}
1464
1465struct CachedGlassLayer<T> {
1466 density: u32,
1467 light: [u32; 2],
1468 dynamics: GlassDynamics,
1469 value: T,
1470}
1471
1472fn cached_glass_layer(resolved: ResolvedGlass) -> impl Fn(f32, GlassDynamics) -> GraphicsLayer {
1473 cached_glass_layers(resolved, false, std::convert::identity)
1474}
1475
1476pub(crate) fn cached_glass_content_layers(
1477 resolved: ResolvedGlass,
1478) -> impl Fn(f32, GlassDynamics) -> [GraphicsLayer; 2] {
1479 cached_glass_layers(resolved, true, |mut layer| {
1480 let Some(RenderEffect::Chain { first, second }) = layer.backdrop_effect.take() else {
1481 panic!("content layers require a sequential edge lens");
1482 };
1483 layer.backdrop_effect = Some((*second).clone());
1484 [
1485 GraphicsLayer {
1486 backdrop_effect: Some((*first).clone()),
1487 ..Default::default()
1488 },
1489 layer,
1490 ]
1491 })
1492}
1493
1494fn cached_glass_layers<T: Clone>(
1495 resolved: ResolvedGlass,
1496 separate_content: bool,
1497 assemble: impl Fn(GraphicsLayer) -> T,
1498) -> impl Fn(f32, GlassDynamics) -> T {
1499 let cached = RefCell::new(None::<CachedGlassLayer<T>>);
1500 move |density, dynamics| {
1501 let direction = glass_light_direction();
1502 let light = [direction.0, direction.1].map(f32::to_bits);
1503 let mut cached = cached.borrow_mut();
1504 if let Some(previous) = &*cached
1505 && previous.density == density.to_bits()
1506 && previous.light == light
1507 && glass_dynamics_match(&previous.dynamics, &dynamics)
1508 {
1509 return previous.value.clone();
1510 }
1511 let render_effect = ((!resolved.clip && dynamics.morph.is_some())
1512 || (resolved.clip && resolved.clips_in_shader()))
1513 .then(|| resolved.runtime_effect(density, &dynamics, true));
1514 let layer = GraphicsLayer {
1515 backdrop_effect: Some(resolved.runtime_effect_with_content(
1516 density,
1517 &dynamics,
1518 false,
1519 separate_content,
1520 )),
1521 render_effect,
1522 shape: resolved.shape.layer_shape(),
1523 clip: resolved.clip && !resolved.clips_in_shader(),
1524 ..Default::default()
1525 };
1526 let value = assemble(layer);
1527 *cached = Some(CachedGlassLayer {
1528 density: density.to_bits(),
1529 light,
1530 dynamics,
1531 value: value.clone(),
1532 });
1533 value
1534 }
1535}
1536
1537fn glass_dynamics_match(a: &GlassDynamics, b: &GlassDynamics) -> bool {
1538 let bits = |d: &GlassDynamics| {
1539 let GlassDynamics {
1540 edge_return_depth_dp,
1541 optical_projection,
1542 activity,
1543 resting_tint,
1544 highlight_boost,
1545 saturation_boost,
1546 tint_alpha_multiplier,
1547 morph: _,
1548 touch,
1549 touch_radius_dp,
1550 press_depth,
1551 } = d;
1552 (
1553 edge_return_depth_dp.map(f32::to_bits),
1554 optical_projection.map(|(x, y)| [x, y].map(f32::to_bits)),
1555 activity.map(f32::to_bits),
1556 resting_tint.map(|Color(r, g, b, a)| [r, g, b, a].map(f32::to_bits)),
1557 [*highlight_boost, *saturation_boost].map(f32::to_bits),
1558 tint_alpha_multiplier.map(f32::to_bits),
1559 touch.map(|(x, y, strength)| [x, y, strength].map(f32::to_bits)),
1560 press_depth.map(f32::to_bits),
1561 touch_radius_dp.map(f32::to_bits),
1562 )
1563 };
1564 bits(a) == bits(b)
1565 && match (&a.morph, &b.morph) {
1566 (None, None) => true,
1567 (Some(a), Some(b)) => glass_morphs_match(a, b),
1568 _ => false,
1569 }
1570}
1571
1572fn glass_morphs_match(a: &GlassMorph, b: &GlassMorph) -> bool {
1573 let shape_bits =
1574 |&(x, y, w, h, r): &(f32, f32, f32, f32, f32)| [x, y, w, h, r].map(f32::to_bits);
1575 let bits = |m: &GlassMorph| {
1576 let GlassMorph {
1577 node_size,
1578 primary,
1579 shapes: _,
1580 glue,
1581 wobble_amplitude,
1582 wobble_phase,
1583 bulge_amplitude,
1584 bulge_direction,
1585 ellipse_blend,
1586 capsule_smoothing_dp,
1587 deformation,
1588 zoom_anchor,
1589 } = m;
1590 (
1591 [
1592 node_size.0,
1593 node_size.1,
1594 *glue,
1595 *wobble_amplitude,
1596 *wobble_phase,
1597 *bulge_amplitude,
1598 *bulge_direction,
1599 *ellipse_blend,
1600 *capsule_smoothing_dp,
1601 zoom_anchor.0,
1602 zoom_anchor.1,
1603 ]
1604 .map(f32::to_bits),
1605 shape_bits(primary),
1606 deformation
1607 .map(|GlassDeformation { axis, along }| [axis.0, axis.1, along].map(f32::to_bits)),
1608 )
1609 };
1610 bits(a) == bits(b)
1611 && a.shapes
1612 .iter()
1613 .map(shape_bits)
1614 .eq(b.shapes.iter().map(shape_bits))
1615}
1616
1617fn morph_output_support(
1618 morph: &GlassMorph,
1619 activity: f32,
1620 morph_pad: f32,
1621 shadow_reach: f32,
1622 shadow_offset: f32,
1623) -> Rect {
1624 let (px, py, pw, ph, _) = morph.primary;
1625 let (along, (ax, ay)) = morph.deformation.map_or((1.0, (1.0, 0.0)), |deformation| {
1626 (
1627 1.0 + (deformation.along() - 1.0) * activity,
1628 deformation.axis(),
1629 )
1630 });
1631 let across = 1.0 / along;
1632 let smallest_strain = along.min(across);
1633 let reach = (morph_pad + shadow_reach + 4.0) / smallest_strain;
1634 let rows = [
1635 (
1636 (along * ax * ax + across * ay * ay).abs(),
1637 ((along - across) * ax * ay).abs(),
1638 ),
1639 (
1640 ((along - across) * ax * ay).abs(),
1641 (along * ay * ay + across * ax * ax).abs(),
1642 ),
1643 ];
1644 let half = (pw * 0.5 + reach, ph * 0.5 + reach);
1645 let extent_x = rows[0].0 * half.0 + rows[0].1 * half.1;
1646 let extent_y = rows[1].0 * half.0 + rows[1].1 * half.1;
1647 let (mut left, mut top) = (px - extent_x, py - extent_y);
1648 let (mut right, mut bottom) = (px + extent_x, py + extent_y);
1649 for (sx, sy, sw, sh, _) in &morph.shapes {
1650 left = left.min(sx - sw * 0.5 - reach);
1651 top = top.min(sy - sh * 0.5 - reach);
1652 right = right.max(sx + sw * 0.5 + reach);
1653 bottom = bottom.max(sy + sh * 0.5 + reach);
1654 }
1655 Rect {
1656 x: left,
1657 y: top - shadow_offset,
1658 width: right - left,
1659 height: bottom - top + 2.0 * shadow_offset,
1660 }
1661}
1662
1663fn set_morph_uniforms(shader: &mut RuntimeShader, morph: &GlassMorph, activity: f32) {
1664 let (node_w, node_h) = morph.node_size;
1665 let (cx, cy, w, h, radius) = morph.primary;
1666 shader.set_float2(0, node_w.max(1.0), node_h.max(1.0));
1667 shader.set_float2(2, cx, cy);
1668 shader.set_float2(4, w, h);
1669 shader.set_float(6, radius);
1670 let count = morph.shapes.len().min(GlassMorph::MAX_SHAPES);
1671 shader.set_float(30, count as f32);
1672 for (index, (sx, sy, sw, sh, sr)) in morph.shapes.iter().take(count).enumerate() {
1673 let base = 36 + index * 5;
1674 shader.set_float(base, *sx);
1675 shader.set_float(base + 1, *sy);
1676 shader.set_float(base + 2, *sw);
1677 shader.set_float(base + 3, *sh);
1678 shader.set_float(base + 4, *sr);
1679 }
1680 shader.set_float(31, morph.glue);
1681 shader.set_float(32, morph.wobble_amplitude * activity);
1682 shader.set_float(33, morph.wobble_phase);
1683 shader.set_float(26, morph.bulge_amplitude * activity);
1684 shader.set_float(27, morph.bulge_direction);
1685 shader.set_float(110, morph.ellipse_blend.clamp(0.0, 1.0) * activity);
1686 shader.set_float(
1687 cranpose_ui_graphics::liquid_glass::GLASS_CAPSULE_SMOOTHING_UNIFORM,
1688 if morph.capsule_smoothing_dp.is_finite() {
1689 morph.capsule_smoothing_dp.max(0.0) * activity
1690 } else {
1691 0.0
1692 },
1693 );
1694 if let Some(deformation) = morph.deformation {
1695 let axis = deformation.axis();
1696 let along = 1.0 + (deformation.along() - 1.0) * activity;
1697 shader.set_float2(106, axis.0, axis.1);
1698 shader.set_float(108, along);
1699 shader.set_float(109, 1.0 / along);
1700 } else {
1701 shader.set_float2(106, 1.0, 0.0);
1702 shader.set_float2(108, 1.0, 1.0);
1703 }
1704}
1705
1706#[cfg(test)]
1707#[path = "tests/material_tests.rs"]
1708mod tests;
1709
1710#[cfg(test)]
1711#[path = "tests/material_transparency_tests.rs"]
1712mod transparency_tests;