From fa85f69e47d80eeacd93c71f7a208e1cd1c0e26a Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Wed, 24 Apr 2024 14:56:32 +0300 Subject: [PATCH] Add missing `overlayColor` property in `styleFrom` methods (#146685) fixes [Add missing `overlayColor` property in `styleFrom` methods](https://github.com/flutter/flutter/issues/146636) ### Code sample
expand to view the code sample ```dart import 'package:flutter/material.dart'; enum Sizes { extraSmall, small, medium, large, extraLarge } void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text('styleFrom(overlayColor: Colors.red)', style: Theme.of(context).textTheme.titleLarge), TextButton( style: TextButton.styleFrom(overlayColor: Colors.red), onPressed: () {}, child: const Text('TextButton'), ), IconButton( style: IconButton.styleFrom( overlayColor: Colors.red, ), onPressed: () {}, icon: const Icon(Icons.add), ), MenuBar( children: [ MenuItemButton( style: MenuItemButton.styleFrom(overlayColor: Colors.red), child: const Text('MenuItemButton'), onPressed: () {}, ), SubmenuButton( style: SubmenuButton.styleFrom(overlayColor: Colors.red), menuChildren: [ MenuItemButton( child: const Text('MenuItemButton'), onPressed: () {}, ), ], child: const Text('SubmenuButton'), ), ], ), SegmentedButton( style: SegmentedButton.styleFrom(overlayColor: Colors.red), segments: const >[ ButtonSegment( value: Sizes.extraSmall, label: Text('XS')), ButtonSegment(value: Sizes.small, label: Text('S')), ButtonSegment(value: Sizes.medium, label: Text('M')), ButtonSegment( value: Sizes.large, label: Text('L'), ), ButtonSegment( value: Sizes.extraLarge, label: Text('XL')), ], selected: const {Sizes.medium}, onSelectionChanged: (Set newSelection) {}, multiSelectionEnabled: true, ), ], ), ), ), ); } } ```
### Preview ![ScreenRecording2024-04-12at15 25 58-ezgif com-video-to-gif-converter](https://github.com/flutter/flutter/assets/48603081/89b9638d-f369-4ef1-b501-17c9c74b2541) --- .../lib/segmented_button_template.dart | 14 +- .../flutter/lib/src/material/icon_button.dart | 52 +++-- .../flutter/lib/src/material/menu_anchor.dart | 4 + .../lib/src/material/segmented_button.dart | 34 +-- .../test/material/icon_button_test.dart | 195 +++++++++++++++--- .../test/material/icon_button_theme_test.dart | 59 ++++++ .../test/material/menu_anchor_test.dart | 77 +++++++ .../test/material/segmented_button_test.dart | 156 +++++++++++++- .../material/segmented_button_theme_test.dart | 94 +++++++++ 9 files changed, 611 insertions(+), 74 deletions(-) diff --git a/dev/tools/gen_defaults/lib/segmented_button_template.dart b/dev/tools/gen_defaults/lib/segmented_button_template.dart index 4d1254ea109a4..b23b8129cc1af 100644 --- a/dev/tools/gen_defaults/lib/segmented_button_template.dart +++ b/dev/tools/gen_defaults/lib/segmented_button_template.dart @@ -120,27 +120,27 @@ class _${blockName}DefaultsM3 extends SegmentedButtonThemeData { @override Widget? get selectedIcon => const Icon(Icons.check); - static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor){ + static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor, Color? overlayColor){ return MaterialStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return selectedColor?.withOpacity(0.08); + return (overlayColor ?? selectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } } else { if (states.contains(MaterialState.pressed)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return unselectedColor?.withOpacity(0.08); + return (overlayColor ?? unselectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } } return Colors.transparent; diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 54408b50f269f..30895a62fc2ad 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -573,9 +573,15 @@ class IconButton extends StatelessWidget { /// [ButtonStyle.foregroundColor] value. Specify a value for [foregroundColor] /// to specify the color of the button's icons. The [hoverColor], [focusColor] /// and [highlightColor] colors are used to indicate the hover, focus, - /// and pressed states. Use [backgroundColor] for the button's background - /// fill color. Use [disabledForegroundColor] and [disabledBackgroundColor] - /// to specify the button's disabled icon and fill color. + /// and pressed states if [overlayColor] isn't specified. + /// + /// If [overlayColor] is specified and its value is [Colors.transparent] + /// then the pressed/focused/hovered highlights are effectively defeated. + /// Otherwise a [MaterialStateProperty] with the same opacities as the + /// default is created. + /// + /// Use [backgroundColor] for the button's background fill color. Use [disabledForegroundColor] + /// and [disabledBackgroundColor] to specify the button's disabled icon and fill color. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] /// parameters are used to construct [ButtonStyle].mouseCursor. @@ -611,6 +617,7 @@ class IconButton extends StatelessWidget { Color? highlightColor, Color? shadowColor, Color? surfaceTintColor, + Color? overlayColor, double? elevation, Size? minimumSize, Size? fixedSize, @@ -629,20 +636,24 @@ class IconButton extends StatelessWidget { InteractiveInkFeatureFactory? splashFactory, }) { final MaterialStateProperty? buttonBackgroundColor = (backgroundColor == null && disabledBackgroundColor == null) - ? null - : _IconButtonDefaultBackground(backgroundColor, disabledBackgroundColor); + ? null + : _IconButtonDefaultBackground(backgroundColor, disabledBackgroundColor); final MaterialStateProperty? buttonForegroundColor = (foregroundColor == null && disabledForegroundColor == null) + ? null + : _IconButtonDefaultForeground(foregroundColor, disabledForegroundColor); + final MaterialStateProperty? overlayColorProp = (foregroundColor == null && + hoverColor == null && focusColor == null && highlightColor == null && overlayColor == null) ? null - : _IconButtonDefaultForeground(foregroundColor, disabledForegroundColor); - final MaterialStateProperty? overlayColor = (foregroundColor == null && hoverColor == null && focusColor == null && highlightColor == null) - ? null - : _IconButtonDefaultOverlay(foregroundColor, focusColor, hoverColor, highlightColor); + : switch (overlayColor) { + (final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), + _ => _IconButtonDefaultOverlay(foregroundColor, focusColor, hoverColor, highlightColor, overlayColor), + }; final MaterialStateProperty mouseCursor = _IconButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( backgroundColor: buttonBackgroundColor, foregroundColor: buttonForegroundColor, - overlayColor: overlayColor, + overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), elevation: ButtonStyleButton.allOrNull(elevation), @@ -1023,34 +1034,41 @@ class _IconButtonDefaultForeground extends MaterialStateProperty { @immutable class _IconButtonDefaultOverlay extends MaterialStateProperty { - _IconButtonDefaultOverlay(this.foregroundColor, this.focusColor, this.hoverColor, this.highlightColor); + _IconButtonDefaultOverlay( + this.foregroundColor, + this.focusColor, + this.hoverColor, + this.highlightColor, + this.overlayColor, + ); final Color? foregroundColor; final Color? focusColor; final Color? hoverColor; final Color? highlightColor; + final Color? overlayColor; @override Color? resolve(Set states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { - return highlightColor ?? foregroundColor?.withOpacity(0.1); + return highlightColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return hoverColor ?? foregroundColor?.withOpacity(0.08); + return hoverColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return focusColor ?? foregroundColor?.withOpacity(0.1); + return focusColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } } if (states.contains(MaterialState.pressed)) { - return highlightColor ?? foregroundColor?.withOpacity(0.1); + return highlightColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return hoverColor ?? foregroundColor?.withOpacity(0.08); + return hoverColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return focusColor ?? foregroundColor?.withOpacity(0.1); + return focusColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } return null; } diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 630b92a859c5e..3953f9ee833d3 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -1022,6 +1022,7 @@ class MenuItemButton extends StatefulWidget { Color? surfaceTintColor, Color? iconColor, TextStyle? textStyle, + Color? overlayColor, double? elevation, EdgeInsetsGeometry? padding, Size? minimumSize, @@ -1047,6 +1048,7 @@ class MenuItemButton extends StatefulWidget { surfaceTintColor: surfaceTintColor, iconColor: iconColor, textStyle: textStyle, + overlayColor: overlayColor, elevation: elevation, padding: padding, minimumSize: minimumSize, @@ -1775,6 +1777,7 @@ class SubmenuButton extends StatefulWidget { Color? surfaceTintColor, Color? iconColor, TextStyle? textStyle, + Color? overlayColor, double? elevation, EdgeInsetsGeometry? padding, Size? minimumSize, @@ -1800,6 +1803,7 @@ class SubmenuButton extends StatefulWidget { surfaceTintColor: surfaceTintColor, iconColor: iconColor, textStyle: textStyle, + overlayColor: overlayColor, elevation: elevation, padding: padding, minimumSize: minimumSize, diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index ba639de35f1fa..cbfcb2dfed2eb 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -203,7 +203,12 @@ class SegmentedButton extends StatefulWidget { /// /// The [foregroundColor], [selectedForegroundColor], and [disabledForegroundColor] /// colors are used to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], - /// and a derived [ButtonStyle.overlayColor]. + /// and a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. + /// + /// If [overlayColor] is specified and its value is [Colors.transparent] + /// then the pressed/focused/hovered highlights are effectively defeated. + /// Otherwise a [MaterialStateProperty] with the same opacities as the + /// default is created. /// /// The [backgroundColor], [selectedBackgroundColor] and [disabledBackgroundColor] /// colors are used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. @@ -261,6 +266,7 @@ class SegmentedButton extends StatefulWidget { Color? disabledBackgroundColor, Color? shadowColor, Color? surfaceTintColor, + Color? overlayColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -286,9 +292,13 @@ class SegmentedButton extends StatefulWidget { (backgroundColor == null && disabledBackgroundColor == null && selectedBackgroundColor == null) ? null : _SegmentButtonDefaultColor(backgroundColor, disabledBackgroundColor, selectedBackgroundColor); - final MaterialStateProperty? overlayColor = (foregroundColor == null && selectedForegroundColor == null) - ? null - : _SegmentedButtonDefaultsM3.resolveStateColor(foregroundColor, selectedForegroundColor); + final MaterialStateProperty? overlayColorProp = (foregroundColor == null && + selectedForegroundColor == null && overlayColor == null) + ? null + : switch (overlayColor) { + (final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), + _ => _SegmentedButtonDefaultsM3.resolveStateColor(foregroundColor, selectedForegroundColor, overlayColor), + }; return TextButton.styleFrom( textStyle: textStyle, shadowColor: shadowColor, @@ -311,7 +321,7 @@ class SegmentedButton extends StatefulWidget { ).copyWith( foregroundColor: foregroundColorProp, backgroundColor: backgroundColorProp, - overlayColor: overlayColor, + overlayColor: overlayColorProp, ); } @@ -1066,27 +1076,27 @@ class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData { @override Widget? get selectedIcon => const Icon(Icons.check); - static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor){ + static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor, Color? overlayColor){ return MaterialStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return selectedColor?.withOpacity(0.08); + return (overlayColor ?? selectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } } else { if (states.contains(MaterialState.pressed)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return unselectedColor?.withOpacity(0.08); + return (overlayColor ?? unselectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } } return Colors.transparent; diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart index 2a74cf2764d9e..38236b7d14473 100644 --- a/packages/flutter/test/material/icon_button_test.dart +++ b/packages/flutter/test/material/icon_button_test.dart @@ -7,6 +7,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -27,6 +28,10 @@ void main() { mockOnPressedFunction = MockOnPressedFunction(); }); + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + testWidgets('test icon is findable by key', (WidgetTester tester) async { const ValueKey key = ValueKey('icon-button'); await tester.pumpWidget( @@ -1222,10 +1227,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1234,12 +1235,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1249,7 +1250,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); focusNode.dispose(); }); @@ -1363,10 +1364,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1375,12 +1372,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1390,7 +1387,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); focusNode.dispose(); }); @@ -1619,10 +1616,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1631,12 +1624,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1646,7 +1639,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); focusNode.dispose(); }); @@ -1875,10 +1868,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1887,12 +1876,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1902,7 +1891,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); focusNode.dispose(); }); @@ -2586,6 +2575,156 @@ void main() { expect(box.size, equals(const Size(48, 48))); }); + testWidgets('IconButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); + + testWidgets('IconButton.styleFrom highlight, hover, focus colors overrides overlayColor', (WidgetTester tester) async { + const Color hoverColor = Color(0xff0000f2); + const Color highlightColor = Color(0xff0000f1); + const Color focusColor = Color(0xff0000f3); + const Color overlayColor = Color(0xffff0000); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButton( + style: IconButton.styleFrom( + hoverColor: hoverColor, + highlightColor: highlightColor, + focusColor: focusColor, + overlayColor: overlayColor, + ), + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: hoverColor)); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: hoverColor) + ..rect(color: highlightColor), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: focusColor)); + }); + + testWidgets('IconButton.styleFrom with transparent overlayColor', (WidgetTester tester) async { + const Color overlayColor = Colors.transparent; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor) + ..rect(color: overlayColor), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + }); + group('IconTheme tests in Material 3', () { testWidgets('IconTheme overrides default values in M3', (WidgetTester tester) async { // Theme's IconTheme diff --git a/packages/flutter/test/material/icon_button_theme_test.dart b/packages/flutter/test/material/icon_button_theme_test.dart index 4acf1807f82ba..dce90d24a624c 100644 --- a/packages/flutter/test/material/icon_button_theme_test.dart +++ b/packages/flutter/test/material/icon_button_theme_test.dart @@ -2,10 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + test('IconButtonThemeData lerp special cases', () { expect(IconButtonThemeData.lerp(null, null, 0), null); const IconButtonThemeData data = IconButtonThemeData(); @@ -254,4 +260,57 @@ void main() { material = tester.widget(buttonMaterialFinder); expect(material.shadowColor, shadowColor); }); + + testWidgets('IconButtonTheme IconButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + overlayColor: overlayColor, + ), + ), + child: IconButton( + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); } diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 3490734468a3c..5ff7ef894265b 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -166,6 +166,10 @@ void main() { ); } + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + testWidgets('Menu responds to density changes', (WidgetTester tester) async { Widget buildMenu({VisualDensity? visualDensity = VisualDensity.standard}) { return MaterialApp( @@ -2494,6 +2498,40 @@ void main() { final AssertionError exception = tester.takeException() as AssertionError; expect(exception, isAssertionError); }, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/99933 + + testWidgets('MenuItemButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: MenuItemButton( + style: MenuItemButton.styleFrom(overlayColor: overlayColor), + onPressed: () {}, + child: const Text('MenuItem'), + ), + ), + )); + + // Hovered. + final Offset center = tester.getCenter(find.byType(MenuItemButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + }); }); group('Layout', () { @@ -3709,6 +3747,45 @@ void main() { ); expect(intrinsicWidthFinder, findsOneWidget); }); + + testWidgets('SubmenuButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff00ff); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: SubmenuButton( + style: SubmenuButton.styleFrom(overlayColor: overlayColor), + menuChildren: [ + MenuItemButton( + onPressed: () {}, + child: const Text('MenuItemButton'), + ), + ], + child: const Text('Submenu'), + ), + ), + )); + + // Hovered. + final Offset center = tester.getCenter(find.byType(SubmenuButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + }); } List createTestMenus({ diff --git a/packages/flutter/test/material/segmented_button_test.dart b/packages/flutter/test/material/segmented_button_test.dart index d764d74ac02c4..c4a2f2bcd066b 100644 --- a/packages/flutter/test/material/segmented_button_test.dart +++ b/packages/flutter/test/material/segmented_button_test.dart @@ -9,6 +9,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; @@ -21,6 +22,10 @@ Widget boilerplate({required Widget child}) { } void main() { + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + testWidgets('SegmentsButton when compositing does not crash', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/135747 // If the render object holds on to a stale canvas reference, this will @@ -584,10 +589,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - final Material material = tester.widget(find.descendant( of: find.byType(TextButton), matching: find.byType(Material), @@ -601,13 +602,13 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.08))); expect(material.textStyle?.color, theme.colorScheme.onSurface); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); expect(material.textStyle?.color, theme.colorScheme.onSurface); }); @@ -763,16 +764,13 @@ void main() { ); // Test foreground color is applied to the overlay color. - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } final TestGesture gesture = await tester.createGesture( kind: PointerDeviceKind.mouse, ); await gesture.addPointer(); await gesture.down(tester.getCenter(find.text('1'))); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: foregroundColor.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: foregroundColor.withOpacity(0.08))); }); testWidgets('Disabled SegmentedButton has correct states when rebuilding', (WidgetTester tester) async { @@ -909,6 +907,144 @@ void main() { // The width of the SegmentedButton must be less than the width of the parent widget. expect(segmentedButtonWidth, lessThan(screenWidth)); }, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/145527 + + testWidgets('SegmentedButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SegmentedButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Option 1'), + ), + ButtonSegment( + value: 1, + label: Text('Option 2'), + ), + ], + onSelectionChanged: (Set selected) {}, + selected: const {1}, + ), + ), + ), + ), + ); + + // Hovered selected segment, + Offset center = tester.getCenter(find.text('Option 1')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Hovered unselected segment, + center = tester.getCenter(find.text('Option 2')); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted unselected segment (pressed). + center = tester.getCenter(find.text('Option 1')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Highlighted selected segment (pressed) + center = tester.getCenter(find.text('Option 2')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused unselected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + + // Focused selected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); + + testWidgets('SegmentedButton.styleFrom with transparent overlayColor', (WidgetTester tester) async { + const Color overlayColor = Colors.transparent; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SegmentedButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Option'), + ), + ], + onSelectionChanged: (Set selected) {}, + selected: const {0}, + ), + ), + ), + ), + ); + + // Hovered, + final Offset center = tester.getCenter(find.text('Option')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor) + ..rect(color: overlayColor), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + }); } Set enabled = const {}; diff --git a/packages/flutter/test/material/segmented_button_theme_test.dart b/packages/flutter/test/material/segmented_button_theme_test.dart index f978b4bb4a768..74c5062bf0de2 100644 --- a/packages/flutter/test/material/segmented_button_theme_test.dart +++ b/packages/flutter/test/material/segmented_button_theme_test.dart @@ -3,10 +3,15 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } test('SegmentedButtonThemeData copyWith, ==, hashCode basics', () { expect(const SegmentedButtonThemeData(), const SegmentedButtonThemeData().copyWith()); @@ -476,4 +481,93 @@ void main() { expect(selectedIcon, findsNothing); } }); + + testWidgets('SegmentedButtonTheme SegmentedButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + segmentedButtonTheme: SegmentedButtonThemeData( + style: SegmentedButton.styleFrom(overlayColor: overlayColor), + ), + ), + home: Scaffold( + body: Center( + child: SegmentedButton( + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Option 1'), + ), + ButtonSegment( + value: 1, + label: Text('Option 2'), + ), + ], + onSelectionChanged: (Set selected) {}, + selected: const {1}, + ), + ), + ), + ), + ); + + // Hovered selected segment, + Offset center = tester.getCenter(find.text('Option 1')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Hovered unselected segment, + center = tester.getCenter(find.text('Option 2')); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted unselected segment (pressed). + center = tester.getCenter(find.text('Option 1')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Highlighted selected segment (pressed) + center = tester.getCenter(find.text('Option 2')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused unselected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + + // Focused selected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); }