diff --git a/dev/tools/gen_defaults/lib/segmented_button_template.dart b/dev/tools/gen_defaults/lib/segmented_button_template.dart index b23b8129cc1..46ac9ce2462 100644 --- a/dev/tools/gen_defaults/lib/segmented_button_template.dart +++ b/dev/tools/gen_defaults/lib/segmented_button_template.dart @@ -120,31 +120,24 @@ class _${blockName}DefaultsM3 extends SegmentedButtonThemeData { @override Widget? get selectedIcon => const Icon(Icons.check); - 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 (overlayColor ?? selectedColor)?.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return (overlayColor ?? selectedColor)?.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return (overlayColor ?? selectedColor)?.withOpacity(0.1); - } - } else { - if (states.contains(MaterialState.pressed)) { - return (overlayColor ?? unselectedColor)?.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return (overlayColor ?? unselectedColor)?.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return (overlayColor ?? unselectedColor)?.withOpacity(0.1); - } - } - return Colors.transparent; - }); + static WidgetStateProperty resolveStateColor( + Color? unselectedColor, + Color? selectedColor, + Color? overlayColor, + ) { + final Color? selected = overlayColor ?? selectedColor; + final Color? unselected = overlayColor ?? unselectedColor; + return WidgetStateProperty.fromMap( + { + WidgetState.selected & WidgetState.pressed: selected?.withOpacity(0.1), + WidgetState.selected & WidgetState.hovered: selected?.withOpacity(0.08), + WidgetState.selected & WidgetState.focused: selected?.withOpacity(0.1), + WidgetState.pressed: unselected?.withOpacity(0.1), + WidgetState.hovered: unselected?.withOpacity(0.08), + WidgetState.focused: unselected?.withOpacity(0.1), + WidgetState.any: Colors.transparent, + }, + ); } } '''; diff --git a/examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart b/examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart index 0b1e202197d..81ff19b5b0a 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart @@ -30,8 +30,8 @@ class MaterialStateExample extends StatelessWidget { Widget build(BuildContext context) { return TextFormField( initialValue: 'abc', - decoration: InputDecoration( - prefixIcon: const Icon(Icons.person), + decoration: const InputDecoration( + prefixIcon: Icon(Icons.person), prefixIconColor: WidgetStateColor.fromMap( { WidgetState.focused: Colors.green, diff --git a/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart b/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart index 3e0d89c1eec..f8f8f4f64d4 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart @@ -32,7 +32,7 @@ class MaterialStateExample extends StatelessWidget { return Theme( data: themeData.copyWith( inputDecorationTheme: themeData.inputDecorationTheme.copyWith( - prefixIconColor: WidgetStateColor.fromMap( + prefixIconColor: const WidgetStateColor.fromMap( { WidgetState.error: Colors.red, WidgetState.focused: Colors.blue, diff --git a/examples/api/lib/material/list_tile/list_tile.3.dart b/examples/api/lib/material/list_tile/list_tile.3.dart index b74fc78fca5..d858b5a47f7 100644 --- a/examples/api/lib/material/list_tile/list_tile.3.dart +++ b/examples/api/lib/material/list_tile/list_tile.3.dart @@ -45,7 +45,7 @@ class _ListTileExampleState extends State { _selected = !_selected; }); }, - iconColor: WidgetStateColor.fromMap({ + iconColor: const WidgetStateColor.fromMap({ WidgetState.disabled: Colors.red, WidgetState.selected: Colors.green, WidgetState.any: Colors.black, diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 6a1c50cdee4..be4015614c7 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -246,6 +246,23 @@ abstract class ButtonStyleButton extends StatefulWidget { /// A convenience method for subclasses. static MaterialStateProperty? allOrNull(T? value) => value == null ? null : MaterialStatePropertyAll(value); + /// Returns null if [enabled] and [disabled] are null. + /// Otherwise, returns a [WidgetStateProperty] that resolves to [disabled] + /// when [WidgetState.disabled] is active, and [enabled] otherwise. + /// + /// A convenience method for subclasses. + static WidgetStateProperty? defaultColor(Color? enabled, Color? disabled) { + if ((enabled ?? disabled) == null) { + return null; + } + return WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabled, + WidgetState.any: enabled, + }, + ); + } + /// A convenience method used by subclasses in the framework, that returns an /// interpolated value based on the [fontSizeMultiplier] parameter: /// diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index f5e4cb52d52..1f5c3623750 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -228,37 +228,39 @@ class ElevatedButton extends ButtonStyleButton { ButtonLayerBuilder? backgroundBuilder, ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _ElevatedButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; - final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, _) => _ElevatedButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _ElevatedButtonDefaultColor(iconColor, disabledIconColor), - }; final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _ElevatedButtonDefaultOverlay((overlayColor ?? foregroundColor)!), + (_, Color(a: 0.0)) => WidgetStatePropertyAll(overlayColor), + (_, final Color color) || (final Color color, _) => WidgetStateProperty.fromMap( + { + WidgetState.pressed: color.withOpacity(0.1), + WidgetState.hovered: color.withOpacity(0.08), + WidgetState.focused: color.withOpacity(0.1), + }, + ), }; - final MaterialStateProperty? elevationValue = switch (elevation) { - null => null, - _ => _ElevatedButtonDefaultElevation(elevation), - }; - final MaterialStateProperty mouseCursor = _ElevatedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); + + WidgetStateProperty? elevationValue; + if (elevation != null) { + elevationValue = WidgetStateProperty.fromMap( + { + WidgetState.disabled: 0, + WidgetState.pressed: elevation + 6, + WidgetState.hovered: elevation + 2, + WidgetState.focused: elevation + 2, + WidgetState.any: elevation, + }, + ); + } return ButtonStyle( textStyle: MaterialStatePropertyAll(textStyle), - backgroundColor: backgroundColorProp, - foregroundColor: foregroundColorProp, + backgroundColor: ButtonStyleButton.defaultColor(backgroundColor, disabledBackgroundColor), + foregroundColor: ButtonStyleButton.defaultColor(foregroundColor, disabledForegroundColor), overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, + iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor), elevation: elevationValue, padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -266,7 +268,12 @@ class ElevatedButton extends ButtonStyleButton { maximumSize: ButtonStyleButton.allOrNull(maximumSize), side: ButtonStyleButton.allOrNull(side), shape: ButtonStyleButton.allOrNull(shape), - mouseCursor: mouseCursor, + mouseCursor: WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabledMouseCursor, + WidgetState.any: enabledMouseCursor, + }, + ), visualDensity: visualDensity, tapTargetSize: tapTargetSize, animationDuration: animationDuration, @@ -453,83 +460,6 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) { ); } -@immutable -class _ElevatedButtonDefaultColor extends MaterialStateProperty with Diagnosticable { - _ElevatedButtonDefaultColor(this.color, this.disabled); - - final Color? color; - final Color? disabled; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabled; - } - return color; - } -} - -@immutable -class _ElevatedButtonDefaultOverlay extends MaterialStateProperty with Diagnosticable { - _ElevatedButtonDefaultOverlay(this.overlay); - - final Color overlay; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.pressed)) { - return overlay.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return overlay.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return overlay.withOpacity(0.1); - } - return null; - } -} - -@immutable -class _ElevatedButtonDefaultElevation extends MaterialStateProperty with Diagnosticable { - _ElevatedButtonDefaultElevation(this.elevation); - - final double elevation; - - @override - double resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return 0; - } - if (states.contains(MaterialState.pressed)) { - return elevation + 6; - } - if (states.contains(MaterialState.hovered)) { - return elevation + 2; - } - if (states.contains(MaterialState.focused)) { - return elevation + 2; - } - return elevation; - } -} - -@immutable -class _ElevatedButtonDefaultMouseCursor extends MaterialStateProperty with Diagnosticable { - _ElevatedButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor); - - final MouseCursor? enabledCursor; - final MouseCursor? disabledCursor; - - @override - MouseCursor? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledCursor; - } - return enabledCursor; - } -} - class _ElevatedButtonWithIcon extends ElevatedButton { _ElevatedButtonWithIcon({ super.key, diff --git a/packages/flutter/lib/src/material/filled_button.dart b/packages/flutter/lib/src/material/filled_button.dart index 6d0a1f225ed..910d4cc846b 100644 --- a/packages/flutter/lib/src/material/filled_button.dart +++ b/packages/flutter/lib/src/material/filled_button.dart @@ -291,33 +291,26 @@ class FilledButton extends ButtonStyleButton { ButtonLayerBuilder? backgroundBuilder, ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _FilledButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; - final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, _) => _FilledButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _FilledButtonDefaultColor(iconColor, disabledIconColor), - }; final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _FilledButtonDefaultOverlay((overlayColor ?? foregroundColor)!), + (_, Color(a: 0.0)) => WidgetStatePropertyAll(overlayColor), + (_, final Color color) || (final Color color, _) => WidgetStateProperty.fromMap( + { + WidgetState.pressed: color.withOpacity(0.1), + WidgetState.hovered: color.withOpacity(0.08), + WidgetState.focused: color.withOpacity(0.1), + }, + ), }; - final MaterialStateProperty mouseCursor = _FilledButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: MaterialStatePropertyAll(textStyle), - backgroundColor: backgroundColorProp, - foregroundColor: foregroundColorProp, + backgroundColor: ButtonStyleButton.defaultColor(backgroundColor, disabledBackgroundColor), + foregroundColor: ButtonStyleButton.defaultColor(foregroundColor, disabledForegroundColor), overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, + iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -325,7 +318,12 @@ class FilledButton extends ButtonStyleButton { maximumSize: ButtonStyleButton.allOrNull(maximumSize), side: ButtonStyleButton.allOrNull(side), shape: ButtonStyleButton.allOrNull(shape), - mouseCursor: mouseCursor, + mouseCursor: WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabledMouseCursor, + WidgetState.any: enabledMouseCursor, + }, + ), visualDensity: visualDensity, tapTargetSize: tapTargetSize, animationDuration: animationDuration, @@ -483,59 +481,6 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) { ); } -@immutable -class _FilledButtonDefaultColor extends MaterialStateProperty with Diagnosticable { - _FilledButtonDefaultColor(this.color, this.disabled); - - final Color? color; - final Color? disabled; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabled; - } - return color; - } -} - -@immutable -class _FilledButtonDefaultOverlay extends MaterialStateProperty with Diagnosticable { - _FilledButtonDefaultOverlay(this.overlay); - - final Color overlay; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.pressed)) { - return overlay.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return overlay.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return overlay.withOpacity(0.1); - } - return null; - } -} - -@immutable -class _FilledButtonDefaultMouseCursor extends MaterialStateProperty with Diagnosticable { - _FilledButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor); - - final MouseCursor? enabledCursor; - final MouseCursor? disabledCursor; - - @override - MouseCursor? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledCursor; - } - return enabledCursor; - } -} - class _FilledButtonWithIcon extends FilledButton { _FilledButtonWithIcon({ super.key, diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 9f321eeef8b..af979f68765 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -643,24 +643,24 @@ class IconButton extends StatelessWidget { AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, }) { - final MaterialStateProperty? buttonBackgroundColor = (backgroundColor == null && disabledBackgroundColor == null) - ? 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 - : 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); + final Color? overlayFallback = overlayColor ?? foregroundColor; + WidgetStateProperty? overlayColorProp; + if ((hoverColor ?? focusColor ?? highlightColor ?? overlayFallback) != null) { + overlayColorProp = switch (overlayColor) { + Color(a: 0.0) => WidgetStatePropertyAll(overlayColor), + _ => WidgetStateProperty.fromMap( + { + WidgetState.pressed: highlightColor ?? overlayFallback?.withOpacity(0.1), + WidgetState.hovered: hoverColor ?? overlayFallback?.withOpacity(0.08), + WidgetState.focused: focusColor ?? overlayFallback?.withOpacity(0.1), + }, + ), + }; + } return ButtonStyle( - backgroundColor: buttonBackgroundColor, - foregroundColor: buttonForegroundColor, + backgroundColor: ButtonStyleButton.defaultColor(backgroundColor, disabledBackgroundColor), + foregroundColor: ButtonStyleButton.defaultColor(foregroundColor, disabledForegroundColor), overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), @@ -672,7 +672,12 @@ class IconButton extends StatelessWidget { iconSize: ButtonStyleButton.allOrNull(iconSize), side: ButtonStyleButton.allOrNull(side), shape: ButtonStyleButton.allOrNull(shape), - mouseCursor: mouseCursor, + mouseCursor: WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabledMouseCursor, + WidgetState.any: enabledMouseCursor, + }, + ), visualDensity: visualDensity, tapTargetSize: tapTargetSize, animationDuration: animationDuration, @@ -993,111 +998,6 @@ class _IconButtonM3 extends ButtonStyleButton { } } -@immutable -class _IconButtonDefaultBackground extends MaterialStateProperty { - _IconButtonDefaultBackground(this.background, this.disabledBackground); - - final Color? background; - final Color? disabledBackground; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledBackground; - } - return background; - } - - @override - String toString() { - return '{disabled: $disabledBackground, otherwise: $background}'; - } -} - -@immutable -class _IconButtonDefaultForeground extends MaterialStateProperty { - _IconButtonDefaultForeground(this.foregroundColor, this.disabledForegroundColor); - - final Color? foregroundColor; - final Color? disabledForegroundColor; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledForegroundColor; - } - return foregroundColor; - } - - @override - String toString() { - return '{disabled: $disabledForegroundColor, otherwise: $foregroundColor}'; - } -} - -@immutable -class _IconButtonDefaultOverlay extends MaterialStateProperty { - _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 ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return hoverColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return focusColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); - } - } - if (states.contains(MaterialState.pressed)) { - return highlightColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return hoverColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return focusColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); - } - return null; - } - - @override - String toString() { - return '{hovered: $hoverColor, focused: $focusColor, pressed: $highlightColor, otherwise: null}'; - } -} - -@immutable -class _IconButtonDefaultMouseCursor extends MaterialStateProperty with Diagnosticable { - _IconButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor); - - final MouseCursor? enabledCursor; - final MouseCursor? disabledCursor; - - @override - MouseCursor? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledCursor; - } - return enabledCursor; - } -} - // BEGIN GENERATED TOKEN PROPERTIES - IconButton // Do not edit by hand. The code between the "BEGIN GENERATED" and diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 72eab40131e..93f523f71cf 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -215,34 +215,30 @@ class OutlinedButton extends ButtonStyleButton { ButtonLayerBuilder? backgroundBuilder, ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _OutlinedButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, null) => MaterialStatePropertyAll(backgroundColor), - (_, _) => _OutlinedButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _OutlinedButtonDefaultColor(iconColor, disabledIconColor), + (_?, null) => WidgetStatePropertyAll(backgroundColor), + (_, _) => ButtonStyleButton.defaultColor(backgroundColor, disabledBackgroundColor), }; final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _OutlinedButtonDefaultOverlay((overlayColor ?? foregroundColor)!), + (_, Color(a: 0.0)) => WidgetStatePropertyAll(overlayColor), + (_, final Color color) || (final Color color, _) => WidgetStateProperty.fromMap( + { + WidgetState.pressed: color.withOpacity(0.1), + WidgetState.hovered: color.withOpacity(0.08), + WidgetState.focused: color.withOpacity(0.1), + }, + ), }; - final MaterialStateProperty mouseCursor = _OutlinedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: ButtonStyleButton.allOrNull(textStyle), - foregroundColor: foregroundColorProp, + foregroundColor: ButtonStyleButton.defaultColor(foregroundColor, disabledForegroundColor), backgroundColor: backgroundColorProp, overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, + iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -250,7 +246,12 @@ class OutlinedButton extends ButtonStyleButton { maximumSize: ButtonStyleButton.allOrNull(maximumSize), side: ButtonStyleButton.allOrNull(side), shape: ButtonStyleButton.allOrNull(shape), - mouseCursor: mouseCursor, + mouseCursor: WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabledMouseCursor, + WidgetState.any: enabledMouseCursor, + }, + ), visualDensity: visualDensity, tapTargetSize: tapTargetSize, animationDuration: animationDuration, @@ -408,59 +409,6 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) { ); } -@immutable -class _OutlinedButtonDefaultColor extends MaterialStateProperty with Diagnosticable { - _OutlinedButtonDefaultColor(this.color, this.disabled); - - final Color? color; - final Color? disabled; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabled; - } - return color; - } -} - -@immutable -class _OutlinedButtonDefaultOverlay extends MaterialStateProperty with Diagnosticable { - _OutlinedButtonDefaultOverlay(this.foreground); - - final Color foreground; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.pressed)) { - return foreground.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return foreground.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return foreground.withOpacity(0.1); - } - return null; - } -} - -@immutable -class _OutlinedButtonDefaultMouseCursor extends MaterialStateProperty with Diagnosticable { - _OutlinedButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor); - - final MouseCursor? enabledCursor; - final MouseCursor? disabledCursor; - - @override - MouseCursor? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledCursor; - } - return enabledCursor; - } -} - class _OutlinedButtonWithIcon extends OutlinedButton { _OutlinedButtonWithIcon({ super.key, diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index 646fea0fda6..e3fe7c04141 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -291,14 +291,6 @@ class SegmentedButton extends StatefulWidget { AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, }) { - final MaterialStateProperty? foregroundColorProp = - (foregroundColor == null && disabledForegroundColor == null && selectedForegroundColor == null) - ? null - : _SegmentButtonDefaultColor(foregroundColor, disabledForegroundColor, selectedForegroundColor); - final MaterialStateProperty? backgroundColorProp = - (backgroundColor == null && disabledBackgroundColor == null && selectedBackgroundColor == null) - ? null - : _SegmentButtonDefaultColor(backgroundColor, disabledBackgroundColor, selectedBackgroundColor); final MaterialStateProperty? overlayColorProp = (foregroundColor == null && selectedForegroundColor == null && overlayColor == null) ? null @@ -326,12 +318,25 @@ class SegmentedButton extends StatefulWidget { alignment: alignment, splashFactory: splashFactory, ).copyWith( - foregroundColor: foregroundColorProp, - backgroundColor: backgroundColorProp, + foregroundColor: _defaultColor(foregroundColor, disabledForegroundColor, selectedForegroundColor), + backgroundColor: _defaultColor(backgroundColor, disabledBackgroundColor, selectedBackgroundColor), overlayColor: overlayColorProp, ); } + static WidgetStateProperty? _defaultColor(Color? enabled, Color? disabled, Color? selected) { + if ((selected ?? enabled ?? disabled) == null) { + return null; + } + return WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabled, + WidgetState.selected: selected, + WidgetState.any: enabled, + }, + ); + } + /// Customizes this button's appearance. /// /// The following style properties apply to the entire segmented button: @@ -589,26 +594,6 @@ class SegmentedButtonState extends State> { } } -@immutable -class _SegmentButtonDefaultColor extends MaterialStateProperty with Diagnosticable { - _SegmentButtonDefaultColor(this.color, this.disabled, this.selected); - - final Color? color; - final Color? disabled; - final Color? selected; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabled; - } - if (states.contains(MaterialState.selected)) { - return selected; - } - return color; - } -} - class _SegmentedButtonRenderWidget extends MultiChildRenderObjectWidget { const _SegmentedButtonRenderWidget({ super.key, @@ -1081,31 +1066,24 @@ class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData { @override Widget? get selectedIcon => const Icon(Icons.check); - 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 (overlayColor ?? selectedColor)?.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return (overlayColor ?? selectedColor)?.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return (overlayColor ?? selectedColor)?.withOpacity(0.1); - } - } else { - if (states.contains(MaterialState.pressed)) { - return (overlayColor ?? unselectedColor)?.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return (overlayColor ?? unselectedColor)?.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return (overlayColor ?? unselectedColor)?.withOpacity(0.1); - } - } - return Colors.transparent; - }); + static WidgetStateProperty resolveStateColor( + Color? unselectedColor, + Color? selectedColor, + Color? overlayColor, + ) { + final Color? selected = overlayColor ?? selectedColor; + final Color? unselected = overlayColor ?? unselectedColor; + return WidgetStateProperty.fromMap( + { + WidgetState.selected & WidgetState.pressed: selected?.withOpacity(0.1), + WidgetState.selected & WidgetState.hovered: selected?.withOpacity(0.08), + WidgetState.selected & WidgetState.focused: selected?.withOpacity(0.1), + WidgetState.pressed: unselected?.withOpacity(0.1), + WidgetState.hovered: unselected?.withOpacity(0.08), + WidgetState.focused: unselected?.withOpacity(0.1), + WidgetState.any: Colors.transparent, + }, + ); } } diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 7d53b34eb84..04590e82e21 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -222,30 +222,29 @@ class TextButton extends ButtonStyleButton { ButtonLayerBuilder? backgroundBuilder, ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _TextButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, null) => MaterialStatePropertyAll(backgroundColor), - (_, _) => _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor), + (_?, null) => MaterialStatePropertyAll(backgroundColor), + (_, _) => ButtonStyleButton.defaultColor(backgroundColor, disabledBackgroundColor), }; final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, null) => MaterialStatePropertyAll(iconColor), - (_, _) => _TextButtonDefaultColor(iconColor, disabledIconColor), + (_?, null) => MaterialStatePropertyAll(iconColor), + (_, _) => ButtonStyleButton.defaultColor(iconColor, disabledIconColor), }; final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _TextButtonDefaultOverlay((overlayColor ?? foregroundColor)!), + (_, Color(a: 0.0)) => WidgetStatePropertyAll(overlayColor), + (_, final Color color) || (final Color color, _) => WidgetStateProperty.fromMap( + { + WidgetState.pressed: color.withOpacity(0.1), + WidgetState.hovered: color.withOpacity(0.08), + WidgetState.focused: color.withOpacity(0.1), + }, + ), }; - final MaterialStateProperty mouseCursor = _TextButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: ButtonStyleButton.allOrNull(textStyle), - foregroundColor: foregroundColorProp, + foregroundColor: ButtonStyleButton.defaultColor(foregroundColor, disabledForegroundColor), backgroundColor: backgroundColorProp, overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), @@ -258,7 +257,12 @@ class TextButton extends ButtonStyleButton { maximumSize: ButtonStyleButton.allOrNull(maximumSize), side: ButtonStyleButton.allOrNull(side), shape: ButtonStyleButton.allOrNull(shape), - mouseCursor: mouseCursor, + mouseCursor: WidgetStateProperty.fromMap( + { + WidgetState.disabled: disabledMouseCursor, + WidgetState.any: enabledMouseCursor, + }, + ), visualDensity: visualDensity, tapTargetSize: tapTargetSize, animationDuration: animationDuration, @@ -434,69 +438,6 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) { ); } -@immutable -class _TextButtonDefaultColor extends MaterialStateProperty { - _TextButtonDefaultColor(this.color, this.disabled); - - final Color? color; - final Color? disabled; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabled; - } - return color; - } - - @override - String toString() { - return '{disabled: $disabled, otherwise: $color}'; - } -} - -@immutable -class _TextButtonDefaultOverlay extends MaterialStateProperty { - _TextButtonDefaultOverlay(this.primary); - - final Color primary; - - @override - Color? resolve(Set states) { - if (states.contains(MaterialState.pressed)) { - return primary.withOpacity(0.1); - } - if (states.contains(MaterialState.hovered)) { - return primary.withOpacity(0.08); - } - if (states.contains(MaterialState.focused)) { - return primary.withOpacity(0.1); - } - return null; - } - - @override - String toString() { - return '{hovered: ${primary.withOpacity(0.04)}, focused,pressed: ${primary.withOpacity(0.12)}, otherwise: null}'; - } -} - -@immutable -class _TextButtonDefaultMouseCursor extends MaterialStateProperty with Diagnosticable { - _TextButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor); - - final MouseCursor? enabledCursor; - final MouseCursor? disabledCursor; - - @override - MouseCursor? resolve(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledCursor; - } - return enabledCursor; - } -} - class _TextButtonWithIcon extends TextButton { _TextButtonWithIcon({ super.key, diff --git a/packages/flutter/lib/src/widgets/widget_state.dart b/packages/flutter/lib/src/widgets/widget_state.dart index 8b0ed72bca1..2aadfc39f31 100644 --- a/packages/flutter/lib/src/widgets/widget_state.dart +++ b/packages/flutter/lib/src/widgets/widget_state.dart @@ -6,6 +6,7 @@ /// @docImport 'package:flutter/scheduler.dart'; library; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -45,14 +46,77 @@ abstract interface class WidgetStatesConstraint { bool isSatisfiedBy(Set states); } -// A private class, used in [WidgetStateOperators]. -class _WidgetStateOperation implements WidgetStatesConstraint { - const _WidgetStateOperation(this._isSatisfiedBy); +@immutable +sealed class _WidgetStateCombo implements WidgetStatesConstraint { + const _WidgetStateCombo(this.first, this.second); - final bool Function(Set states) _isSatisfiedBy; + final WidgetStatesConstraint first; + final WidgetStatesConstraint second; @override - bool isSatisfiedBy(Set states) => _isSatisfiedBy(states); + // ignore: hash_and_equals, since == is defined in subclasses + int get hashCode => Object.hash(first, second); +} + +class _WidgetStateAnd extends _WidgetStateCombo { + const _WidgetStateAnd(super.first, super.second); + + @override + bool isSatisfiedBy(Set states) { + return first.isSatisfiedBy(states) && second.isSatisfiedBy(states); + } + + @override + // ignore: hash_and_equals, hashCode is defined in the sealed super-class + bool operator ==(Object other) { + return other is _WidgetStateAnd + && other.first == first + && other.second == second; + } + + @override + String toString() => '($first & $second)'; +} + +class _WidgetStateOr extends _WidgetStateCombo { + const _WidgetStateOr(super.first, super.second); + + @override + bool isSatisfiedBy(Set states) { + return first.isSatisfiedBy(states) || second.isSatisfiedBy(states); + } + + @override + // ignore: hash_and_equals, hashCode is defined in the sealed super-class + bool operator ==(Object other) { + return other is _WidgetStateOr + && other.first == first + && other.second == second; + } + + @override + String toString() => '($first | $second)'; +} + +@immutable +class _WidgetStateNot implements WidgetStatesConstraint { + const _WidgetStateNot(this.value); + + final WidgetStatesConstraint value; + + @override + bool isSatisfiedBy(Set states) => !value.isSatisfiedBy(states); + + @override + bool operator ==(Object other) { + return other is _WidgetStateNot && other.value == value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() => '~$value'; } /// These operators can be used inside a [WidgetStateMap] to combine states @@ -67,33 +131,24 @@ class _WidgetStateOperation implements WidgetStatesConstraint { /// the operators can be used without being directly inherited. extension WidgetStateOperators on WidgetStatesConstraint { /// Combines two [WidgetStatesConstraint] values using logical "and". - WidgetStatesConstraint operator &(WidgetStatesConstraint other) { - return _WidgetStateOperation( - (Set states) => isSatisfiedBy(states) && other.isSatisfiedBy(states), - ); - } + WidgetStatesConstraint operator &(WidgetStatesConstraint other) => _WidgetStateAnd(this, other); /// Combines two [WidgetStatesConstraint] values using logical "or". - WidgetStatesConstraint operator |(WidgetStatesConstraint other) { - return _WidgetStateOperation( - (Set states) => isSatisfiedBy(states) || other.isSatisfiedBy(states), - ); - } + WidgetStatesConstraint operator |(WidgetStatesConstraint other) => _WidgetStateOr(this, other); /// Takes a [WidgetStatesConstraint] and applies the logical "not". - WidgetStatesConstraint operator ~() { - return _WidgetStateOperation( - (Set states) => !isSatisfiedBy(states), - ); - } + WidgetStatesConstraint operator ~() => _WidgetStateNot(this); } // A private class, used to create [WidgetState.any]. -class _AlwaysMatch implements WidgetStatesConstraint { - const _AlwaysMatch(); +class _AnyWidgetStates implements WidgetStatesConstraint { + const _AnyWidgetStates(); @override bool isSatisfiedBy(Set states) => true; + + @override + String toString() => 'WidgetState.any'; } /// Interactive states that some of the widgets can take on when receiving input @@ -183,7 +238,7 @@ enum WidgetState implements WidgetStatesConstraint { /// isn't satisfied by the given set of states, consier adding /// [WidgetState.any] as the final [WidgetStateMap] key. /// {@endtemplate} - static const WidgetStatesConstraint any = _AlwaysMatch(); + static const WidgetStatesConstraint any = _AnyWidgetStates(); @override bool isSatisfiedBy(Set states) => states.contains(this); @@ -268,7 +323,7 @@ abstract class WidgetStateColor extends Color implements WidgetStateProperty map) = _WidgetStateColorMapper; + const factory WidgetStateColor.fromMap(WidgetStateMap map) = _WidgetStateColorMapper; /// Returns a [Color] that's to be used when a component is in the specified /// state. @@ -290,18 +345,6 @@ class _WidgetStateColor extends WidgetStateColor { Color resolve(Set states) => _resolve(states); } -class _WidgetStateColorMapper extends WidgetStateColor { - _WidgetStateColorMapper(this.map) - : super(_WidgetStateMapper(map).resolve(_defaultStates).value); - - final WidgetStateMap map; - - static const Set _defaultStates = {}; - - @override - Color resolve(Set states) => _WidgetStateMapper(map).resolve(states); -} - class _WidgetStateColorTransparent extends WidgetStateColor { const _WidgetStateColorTransparent() : super(0x00000000); @@ -309,6 +352,26 @@ class _WidgetStateColorTransparent extends WidgetStateColor { Color resolve(Set states) => const Color(0x00000000); } +@immutable +class _WidgetStateColorMapper extends _WidgetStateColorTransparent { + const _WidgetStateColorMapper(this.map); + + final WidgetStateMap map; + + _WidgetStateMapper get _mapper => _WidgetStateMapper(map); + + @override + Color resolve(Set states) => _mapper.resolve(states); + + @override + bool operator ==(Object other) { + return other is _WidgetStateColorMapper && other.map == map; + } + + @override + int get hashCode => map.hashCode; +} + /// Defines a [MouseCursor] whose value depends on a set of [WidgetState]s which /// represent the interactive state of a component. /// @@ -548,8 +611,18 @@ class _WidgetBorderSideMapper extends WidgetStateBorderSide { final WidgetStateMap map; + _WidgetStateMapper get _mapper => _WidgetStateMapper(map); + @override - BorderSide? resolve(Set states) => _WidgetStateMapper(map).resolve(states); + BorderSide? resolve(Set states) => _mapper.resolve(states); + + @override + bool operator ==(Object other) { + return other is _WidgetBorderSideMapper && other.map == map; + } + + @override + int get hashCode => map.hashCode; } /// Defines an [OutlinedBorder] whose value depends on a set of [WidgetState]s @@ -660,8 +733,18 @@ class _WidgetTextStyleMapper extends WidgetStateTextStyle { final WidgetStateMap map; + _WidgetStateMapper get _mapper => _WidgetStateMapper(map); + @override - TextStyle resolve(Set states) => _WidgetStateMapper(map).resolve(states); + TextStyle resolve(Set states) => _mapper.resolve(states); + + @override + bool operator ==(Object other) { + return other is _WidgetTextStyleMapper && other.map == map; + } + + @override + int get hashCode => map.hashCode; } /// Interface for classes that [resolve] to a value of type `T` based @@ -847,6 +930,7 @@ class _WidgetStatePropertyWith implements WidgetStateProperty { typedef WidgetStateMap = Map; // A private class, used to create the [WidgetStateProperty.fromMap] constructor. +@immutable class _WidgetStateMapper implements WidgetStateProperty { const _WidgetStateMapper(this.map); @@ -872,6 +956,17 @@ class _WidgetStateMapper implements WidgetStateProperty { ); } } + + @override + bool operator ==(Object other) { + return other is _WidgetStateMapper && mapEquals(map, other.map); + } + + @override + int get hashCode => MapEquality().hash(map); + + @override + String toString() => '$map'; } /// Convenience class for creating a [WidgetStateProperty] that @@ -881,6 +976,7 @@ class _WidgetStateMapper implements WidgetStateProperty { /// /// * [MaterialStatePropertyAll], the Material specific version of /// `WidgetStatePropertyAll`. +@immutable class WidgetStatePropertyAll implements WidgetStateProperty { /// Constructs a [WidgetStateProperty] that always resolves to the given @@ -901,6 +997,16 @@ class WidgetStatePropertyAll implements WidgetStateProperty { return 'WidgetStatePropertyAll($value)'; } } + + @override + bool operator ==(Object other) { + return other is WidgetStatePropertyAll + && other.runtimeType == runtimeType + && other.value == value; + } + + @override + int get hashCode => value.hashCode; } /// Manages a set of [WidgetState]s and notifies listeners of changes. diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index 0844a93ee80..670a0565791 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -21,6 +21,44 @@ void main() { expect(dawn.primaryColor, Color.lerp(dark.primaryColor, light.primaryColor, 0.25)); }); + test('ThemeData objects with .styleFrom() members are equal', () { + ThemeData createThemeData() { + return ThemeData( + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.black, + backgroundColor: Colors.black, + elevation: 1.0, + ), + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + foregroundColor: Colors.black, + disabledForegroundColor: Colors.black, + backgroundColor: Colors.black, + disabledBackgroundColor: Colors.black, + overlayColor: Colors.black, + ), + ), + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + hoverColor: Colors.black, + focusColor: Colors.black, + highlightColor: Colors.black, + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + enabledMouseCursor: MouseCursor.defer, + disabledMouseCursor: MouseCursor.uncontrolled, + ), + ), + ); + } + + expect(createThemeData() == createThemeData(), isTrue); + }); + test('Defaults to the default typography for the platform', () { for (final TargetPlatform platform in TargetPlatform.values) { final ThemeData theme = ThemeData(platform: platform, useMaterial3: false);