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