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