@@ -79,7 +79,6 @@ const TextStyle _kActionSheetContentStyle = TextStyle(
79
79
);
80
80
81
81
// Generic constants shared between Dialog and ActionSheet.
82
- const double _kBlurAmount = 20.0 ;
83
82
const double _kCornerRadius = 14.0 ;
84
83
const double _kDividerThickness = 0.3 ;
85
84
@@ -492,20 +491,34 @@ class _CupertinoAlertDialogState extends State<CupertinoAlertDialog> {
492
491
}
493
492
}
494
493
495
- /// Rounded rectangle surface that looks like an iOS popup surface, e.g., alert dialog
496
- /// and action sheet .
494
+ /// An iOS-style component for creating modal overlays like dialogs and action
495
+ /// sheets .
497
496
///
498
- /// A [CupertinoPopupSurface] can be configured to paint or not paint a white
499
- /// color on top of its blurred area. Typical usage should paint white on top
500
- /// of the blur. However, the white paint can be disabled for the purpose of
501
- /// rendering divider gaps for a more complicated layout, e.g., [CupertinoAlertDialog] .
502
- /// Additionally, the white paint can be disabled to render a blurred rounded
503
- /// rectangle without any color (similar to iOS's volume control popup).
497
+ /// By default, [CupertinoPopupSurface] generates a rounded rectangle surface
498
+ /// that applies two effects to the background content:
499
+ ///
500
+ /// 1. Background filter: Saturates and then blurs content behind the surface.
501
+ /// 2. Overlay color: Covers the filtered background with a transparent
502
+ /// surface color. The color adapts to the CupertinoTheme's brightness:
503
+ /// light gray when the ambient [CupertinoTheme] brightness is
504
+ /// [Brightness.light], and dark gray when [Brightness.dark].
505
+ ///
506
+ /// The blur strength can be changed by setting [blurSigma] to a positive value,
507
+ /// or removed by setting the [blurSigma] to 0.
508
+ ///
509
+ /// The saturation effect can be removed for debugging by setting
510
+ /// [debugIsVibrancePainted] to false. The saturation effect is not supported on
511
+ /// web with the skwasm renderer and will not be applied regardless of the value
512
+ /// of [debugIsVibrancePainted] .
513
+ ///
514
+ /// The surface color can be disabled by setting [isSurfacePainted] to false,
515
+ /// which is useful for more complicated layouts, such as rendering divider gaps
516
+ /// in [CupertinoAlertDialog] or rendering custom surface colors.
504
517
///
505
518
/// {@tool dartpad}
506
519
/// This sample shows how to use a [CupertinoPopupSurface] . The [CupertinoPopupSurface]
507
- /// shows a model popup from the bottom of the screen.
508
- /// Toggling the switch to configure its surface color.
520
+ /// shows a modal popup from the bottom of the screen.
521
+ /// Toggle the switch to configure its surface color.
509
522
///
510
523
/// ** See code in examples/api/lib/cupertino/dialog/cupertino_popup_surface.0.dart **
511
524
/// {@end-tool}
@@ -519,9 +532,29 @@ class CupertinoPopupSurface extends StatelessWidget {
519
532
/// Creates an iOS-style rounded rectangle popup surface.
520
533
const CupertinoPopupSurface ({
521
534
super .key,
535
+ this .blurSigma = defaultBlurSigma,
522
536
this .isSurfacePainted = true ,
523
- this .child,
524
- });
537
+ this .debugIsVibrancePainted = true ,
538
+ required this .child,
539
+ }) : assert (blurSigma >= 0 , 'CupertinoPopupSurface requires a non-negative blur sigma.' );
540
+
541
+ /// The strength of the gaussian blur applied to the area beneath this
542
+ /// surface.
543
+ ///
544
+ /// Defaults to [defaultBlurSigma] . Setting [blurSigma] to 0 will remove the
545
+ /// blur filter.
546
+ final double blurSigma;
547
+
548
+ /// Whether or not the area beneath this surface should be saturated with a
549
+ /// [ColorFilter] .
550
+ ///
551
+ /// The appearance of the [ColorFilter] is determined by the [Brightness]
552
+ /// value obtained from the ambient [CupertinoTheme] .
553
+ ///
554
+ /// The vibrance is always painted if asserts are disabled.
555
+ ///
556
+ /// Defaults to true.
557
+ final bool debugIsVibrancePainted;
525
558
526
559
/// Whether or not to paint a translucent white on top of this surface's
527
560
/// blurred background. [isSurfacePainted] should be true for a typical popup
@@ -531,26 +564,148 @@ class CupertinoPopupSurface extends StatelessWidget {
531
564
/// Some popups, like iOS's volume control popup, choose to render a blurred
532
565
/// area without any white paint covering it. To achieve this effect,
533
566
/// [isSurfacePainted] should be set to false.
567
+ ///
568
+ /// Defaults to true.
534
569
final bool isSurfacePainted;
535
570
536
571
/// The widget below this widget in the tree.
537
- final Widget ? child;
572
+ // Because [CupertinoPopupSurface] is composed of proxy boxes, which mimic
573
+ // the size of their child, a [child] is required to ensure that this surface
574
+ // has a size.
575
+ final Widget child;
576
+
577
+ /// The default strength of the blur applied to widgets underlying a
578
+ /// [CupertinoPopupSurface] .
579
+ ///
580
+ /// Eyeballed from the iOS 17 simulator.
581
+ static const double defaultBlurSigma = 30.0 ;
582
+
583
+ /// The default corner radius of a [CupertinoPopupSurface] .
584
+ static const BorderRadius _clipper = BorderRadius .all (Radius .circular (14 ));
585
+
586
+ // The [ColorFilter] matrix used to saturate widgets underlying a
587
+ // [CupertinoPopupSurface] when the ambient [CupertinoThemeData.brightness] is
588
+ // [Brightness.light].
589
+ //
590
+ // To derive this matrix, the saturation matrix was taken from
591
+ // https://docs.rainmeter.net/tips/colormatrix-guide/ and was tweaked to
592
+ // resemble the iOS 17 simulator.
593
+ //
594
+ // The matrix can be derived from the following function:
595
+ // static List<double> get _lightSaturationMatrix {
596
+ // const double lightLumR = 0.26;
597
+ // const double lightLumG = 0.4;
598
+ // const double lightLumB = 0.17;
599
+ // const double saturation = 2.0;
600
+ // const double sr = (1 - saturation) * lightLumR;
601
+ // const double sg = (1 - saturation) * lightLumG;
602
+ // const double sb = (1 - saturation) * lightLumB;
603
+ // return <double>[
604
+ // sr + saturation, sg, sb, 0.0, 0.0,
605
+ // sr, sg + saturation, sb, 0.0, 0.0,
606
+ // sr, sg, sb + saturation, 0.0, 0.0,
607
+ // 0.0, 0.0, 0.0, 1.0, 0.0,
608
+ // ];
609
+ // }
610
+ static const List <double > _lightSaturationMatrix = < double > [
611
+ 1.74 , - 0.40 , - 0.17 , 0.00 , 0.00 ,
612
+ - 0.26 , 1.60 , - 0.17 , 0.00 , 0.00 ,
613
+ - 0.26 , - 0.40 , 1.83 , 0.00 , 0.00 ,
614
+ 0.00 , 0.00 , 0.00 , 1.00 , 0.00
615
+ ];
616
+
617
+ // The [ColorFilter] matrix used to saturate widgets underlying a
618
+ // [CupertinoPopupSurface] when the ambient [CupertinoThemeData.brightness] is
619
+ // [Brightness.dark].
620
+ //
621
+ // To derive this matrix, the saturation matrix was taken from
622
+ // https://docs.rainmeter.net/tips/colormatrix-guide/ and was tweaked to
623
+ // resemble the iOS 17 simulator.
624
+ //
625
+ // The matrix can be derived from the following function:
626
+ // static List<double> get _darkSaturationMatrix {
627
+ // const double additive = 0.3;
628
+ // const double darkLumR = 0.45;
629
+ // const double darkLumG = 0.8;
630
+ // const double darkLumB = 0.16;
631
+ // const double saturation = 1.7;
632
+ // const double sr = (1 - saturation) * darkLumR;
633
+ // const double sg = (1 - saturation) * darkLumG;
634
+ // const double sb = (1 - saturation) * darkLumB;
635
+ // return <double>[
636
+ // sr + saturation, sg, sb, 0.0, additive,
637
+ // sr, sg + saturation, sb, 0.0, additive,
638
+ // sr, sg, sb + saturation, 0.0, additive,
639
+ // 0.0, 0.0, 0.0, 1.0, 0.0,
640
+ // ];
641
+ // }
642
+ static const List <double > _darkSaturationMatrix = < double > [
643
+ 1.39 , - 0.56 , - 0.11 , 0.00 , 0.30 ,
644
+ - 0.32 , 1.14 , - 0.11 , 0.00 , 0.30 ,
645
+ - 0.32 , - 0.56 , 1.59 , 0.00 , 0.30 ,
646
+ 0.00 , 0.00 , 0.00 , 1.00 , 0.00
647
+ ];
648
+
649
+ ImageFilter ? _buildFilter (Brightness ? brightness) {
650
+ bool isVibrancePainted = true ;
651
+ assert (() {
652
+ isVibrancePainted = debugIsVibrancePainted;
653
+ return true ;
654
+ }());
655
+ if ((kIsWeb && ! isSkiaWeb) || ! isVibrancePainted) {
656
+ if (blurSigma == 0 ) {
657
+ return null ;
658
+ }
659
+ return ImageFilter .blur (
660
+ sigmaX: blurSigma,
661
+ sigmaY: blurSigma,
662
+ );
663
+ }
664
+
665
+ final ColorFilter colorFilter = switch (brightness) {
666
+ Brightness .dark => const ColorFilter .matrix (_darkSaturationMatrix),
667
+ Brightness .light || null => const ColorFilter .matrix (_lightSaturationMatrix)
668
+ };
669
+
670
+ if (blurSigma == 0 ) {
671
+ return colorFilter;
672
+ }
673
+
674
+ return ImageFilter .compose (
675
+ inner: colorFilter,
676
+ outer: ImageFilter .blur (
677
+ sigmaX: blurSigma,
678
+ sigmaY: blurSigma,
679
+ ),
680
+ );
681
+ }
538
682
539
683
@override
540
684
Widget build (BuildContext context) {
541
- Widget ? contents = child;
685
+ final ImageFilter ? filter = _buildFilter (CupertinoTheme .maybeBrightnessOf (context));
686
+ Widget contents = child;
687
+
542
688
if (isSurfacePainted) {
543
689
contents = ColoredBox (
544
690
color: CupertinoDynamicColor .resolve (_kDialogColor, context),
545
691
child: contents,
546
692
);
547
693
}
694
+
695
+ if (filter != null ) {
696
+ return ClipRRect (
697
+ borderRadius: _clipper,
698
+ child: BackdropFilter (
699
+ blendMode: BlendMode .src,
700
+ filter: filter,
701
+ child: contents,
702
+ ),
703
+ );
704
+ }
705
+
548
706
return ClipRRect (
549
- borderRadius: const BorderRadius .all (Radius .circular (_kCornerRadius)),
550
- child: BackdropFilter (
551
- filter: ImageFilter .blur (sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
552
- child: contents,
553
- ),
707
+ borderRadius: _clipper,
708
+ child: contents,
554
709
);
555
710
}
556
711
}
@@ -1125,7 +1280,10 @@ class _CupertinoActionSheetState extends State<CupertinoActionSheet> {
1125
1280
child: ClipRRect (
1126
1281
borderRadius: const BorderRadius .all (Radius .circular (12.0 )),
1127
1282
child: BackdropFilter (
1128
- filter: ImageFilter .blur (sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
1283
+ filter: ImageFilter .blur (
1284
+ sigmaX: CupertinoPopupSurface .defaultBlurSigma,
1285
+ sigmaY: CupertinoPopupSurface .defaultBlurSigma,
1286
+ ),
1129
1287
child: _ActionSheetMainSheet (
1130
1288
pressedIndex: _pressedIndex,
1131
1289
onPressedUpdate: _onPressedUpdate,
0 commit comments