Skip to content

Commit 35b0349

Browse files
Improve CupertinoPopupSurface appearance (flutter#151430)
This PR makes the CupertinoPopupSurface more vibrant. Also, the gaussian kernel was switched to 30 from 20 based on comparisons. @dkwingsmt - Looking forward to your dialog and fixes! After is on bottom: <img width="939" alt="image" src="https://github.com/flutter/flutter/assets/59215665/bd020c9b-af87-4342-9a9f-c9f8f7693456"> Notably, because the borders are very transparent, the new version looks more colorful in the sample screencap than it actually is. As such, focus on the individual colors to get a feel for the change. | actual | old | new |--|--|--| | <img width="30" alt="image" src="https://github.com/flutter/flutter/assets/59215665/7de2801d-a2cc-44a4-a660-2692889fed63"> | <img width="28" alt="image" src="https://github.com/flutter/flutter/assets/59215665/48689d82-af15-4612-b4ab-75d584e9b094"> | <img width="30" alt="image" src="https://github.com/flutter/flutter/assets/59215665/7c1075ec-b815-47e0-b822-65a2b63497a0"> | | <img width="24" alt="image" src="https://github.com/flutter/flutter/assets/59215665/2eeefe25-2e1d-4a79-b748-4925d950b9a2"> | <img width="26" alt="image" src="https://github.com/flutter/flutter/assets/59215665/68a2694c-d943-4563-9b5e-9e86e2ee1d58"> | <img width="28" alt="image" src="https://github.com/flutter/flutter/assets/59215665/1932b90a-1719-40f5-828e-41ceafd59e26"> | <img width="22" alt="image" src="https://github.com/flutter/flutter/assets/59215665/0bd22c64-dd37-4262-a7e8-ed610151ab7a"> | <img width="28" alt="image" src="https://github.com/flutter/flutter/assets/59215665/e1738bd2-98d2-491b-9a4a-d2c7cbc5a080"> | <img width="25" alt="image" src="https://github.com/flutter/flutter/assets/59215665/c245d786-19aa-4a14-8df3-029591d1debd"> | Partially addresses flutter#29483 This will need tests, which I will add once I know which tests break due to this commit. Blockers: * flutter#152026
1 parent c56ab6b commit 35b0349

File tree

2 files changed

+526
-21
lines changed

2 files changed

+526
-21
lines changed

packages/flutter/lib/src/cupertino/dialog.dart

Lines changed: 179 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ const TextStyle _kActionSheetContentStyle = TextStyle(
7979
);
8080

8181
// Generic constants shared between Dialog and ActionSheet.
82-
const double _kBlurAmount = 20.0;
8382
const double _kCornerRadius = 14.0;
8483
const 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

Comments
 (0)