@@ -79,7 +79,6 @@ const TextStyle _kActionSheetContentStyle = TextStyle(
7979);
8080
8181// Generic constants shared between Dialog and ActionSheet.
82- const double _kBlurAmount = 20.0 ;
8382const double _kCornerRadius = 14.0 ;
8483const double _kDividerThickness = 0.3 ;
8584
@@ -492,20 +491,34 @@ class _CupertinoAlertDialogState extends State<CupertinoAlertDialog> {
492491 }
493492}
494493
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 .
497496///
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.
504517///
505518/// {@tool dartpad}
506519/// 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.
509522///
510523/// ** See code in examples/api/lib/cupertino/dialog/cupertino_popup_surface.0.dart **
511524/// {@end-tool}
@@ -519,9 +532,29 @@ class CupertinoPopupSurface extends StatelessWidget {
519532 /// Creates an iOS-style rounded rectangle popup surface.
520533 const CupertinoPopupSurface ({
521534 super .key,
535+ this .blurSigma = defaultBlurSigma,
522536 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;
525558
526559 /// Whether or not to paint a translucent white on top of this surface's
527560 /// blurred background. [isSurfacePainted] should be true for a typical popup
@@ -531,26 +564,148 @@ class CupertinoPopupSurface extends StatelessWidget {
531564 /// Some popups, like iOS's volume control popup, choose to render a blurred
532565 /// area without any white paint covering it. To achieve this effect,
533566 /// [isSurfacePainted] should be set to false.
567+ ///
568+ /// Defaults to true.
534569 final bool isSurfacePainted;
535570
536571 /// 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+ }
538682
539683 @override
540684 Widget build (BuildContext context) {
541- Widget ? contents = child;
685+ final ImageFilter ? filter = _buildFilter (CupertinoTheme .maybeBrightnessOf (context));
686+ Widget contents = child;
687+
542688 if (isSurfacePainted) {
543689 contents = ColoredBox (
544690 color: CupertinoDynamicColor .resolve (_kDialogColor, context),
545691 child: contents,
546692 );
547693 }
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+
548706 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,
554709 );
555710 }
556711}
@@ -1125,7 +1280,10 @@ class _CupertinoActionSheetState extends State<CupertinoActionSheet> {
11251280 child: ClipRRect (
11261281 borderRadius: const BorderRadius .all (Radius .circular (12.0 )),
11271282 child: BackdropFilter (
1128- filter: ImageFilter .blur (sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
1283+ filter: ImageFilter .blur (
1284+ sigmaX: CupertinoPopupSurface .defaultBlurSigma,
1285+ sigmaY: CupertinoPopupSurface .defaultBlurSigma,
1286+ ),
11291287 child: _ActionSheetMainSheet (
11301288 pressedIndex: _pressedIndex,
11311289 onPressedUpdate: _onPressedUpdate,
0 commit comments