flutter/packages/flutter/lib/src/material/button_style.dart
Taha Tesser 9e2d9deb28
Add IconAlignment to ButtonStyle and styleFrom methods (#158503)
Fixes [Proposal to add iconAlignment to
ButtonStyle](https://github.com/flutter/flutter/issues/153350)

### Description

This PR refactors buttons `IconAlignment`, adds to `ButtonStyle` and
`styleFrom` methods. Which makes it possible to customize iconAlignment
same way as icon size and color in the `ButtonStyle`.

### Code sample 

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

enum StyleSegment {
  none,
  widgetButtonStyle,
  widgetStyleFrom,
  themeButtonStyle,
  themeStyleFrom
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  StyleSegment _selectedSegment = StyleSegment.none;

  ThemeData? getThemeStyle() => switch (_selectedSegment) {
        StyleSegment.themeButtonStyle => ThemeData(
            textButtonTheme: const TextButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
            elevatedButtonTheme: const ElevatedButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
            outlinedButtonTheme: const OutlinedButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
            filledButtonTheme: const FilledButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
          ),
        StyleSegment.themeStyleFrom => ThemeData(
            textButtonTheme: TextButtonThemeData(
              style: TextButton.styleFrom(
                iconAlignment: IconAlignment.end,
              ),
            ),
            elevatedButtonTheme: const ElevatedButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
            outlinedButtonTheme: const OutlinedButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
            filledButtonTheme: const FilledButtonThemeData(
              style: ButtonStyle(
                iconAlignment: IconAlignment.end,
              ),
            ),
          ),
        _ => null
      };

  ButtonStyle? getTextButtonStyle() => switch (_selectedSegment) {
        StyleSegment.widgetStyleFrom => TextButton.styleFrom(
            iconAlignment: IconAlignment.end,
          ),
        StyleSegment.widgetButtonStyle => const ButtonStyle(
            iconAlignment: IconAlignment.end,
          ),
        _ => null
      };

  ButtonStyle? getElevatedButtonStyle() => switch (_selectedSegment) {
        StyleSegment.widgetStyleFrom => ElevatedButton.styleFrom(
            iconAlignment: IconAlignment.end,
          ),
        StyleSegment.widgetButtonStyle => const ButtonStyle(
            iconAlignment: IconAlignment.end,
          ),
        _ => null
      };

  ButtonStyle? getOutlinedButtonStyle() => switch (_selectedSegment) {
        StyleSegment.widgetStyleFrom => OutlinedButton.styleFrom(
            iconAlignment: IconAlignment.end,
          ),
        StyleSegment.widgetButtonStyle => const ButtonStyle(
            iconAlignment: IconAlignment.end,
          ),
        _ => null
      };

  ButtonStyle? getFilledButtonStyle() => switch (_selectedSegment) {
        StyleSegment.widgetStyleFrom => FilledButton.styleFrom(
            iconAlignment: IconAlignment.end,
          ),
        StyleSegment.widgetButtonStyle => const ButtonStyle(
            iconAlignment: IconAlignment.end,
          ),
        _ => null
      };

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: getThemeStyle(),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('ButtonStyle Icon Alignment'),
        ),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              spacing: 20,
              children: [
                Wrap(
                  spacing: 16,
                  runSpacing: 16,
                  children: [
                    TextButton.icon(
                      style: getTextButtonStyle(),
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('Text Button'),
                    ),
                    ElevatedButton.icon(
                      style: getElevatedButtonStyle(),
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('Elevated Button'),
                    ),
                    OutlinedButton.icon(
                      style: getOutlinedButtonStyle(),
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('Outlined Button'),
                    ),
                    FilledButton.icon(
                      style: getFilledButtonStyle(),
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('Filled Button'),
                    ),
                    FilledButton.tonalIcon(
                      style: getFilledButtonStyle(),
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('Filled Button Tonal Icon'),
                    ),
                  ],
                ),
                StyleSelection(
                  selectedSegment: _selectedSegment,
                  onSegmentSelected: (StyleSegment segment) {
                    setState(() {
                      _selectedSegment = segment;
                    });
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class StyleSelection extends StatelessWidget {
  const StyleSelection(
      {super.key,
      this.selectedSegment = StyleSegment.none,
      required this.onSegmentSelected});

  final ValueChanged<StyleSegment> onSegmentSelected;
  final StyleSegment selectedSegment;

  @override
  Widget build(BuildContext context) {
    return SegmentedButton<StyleSegment>(
      segments: const <ButtonSegment<StyleSegment>>[
        ButtonSegment<StyleSegment>(
          value: StyleSegment.none,
          label: Text('None'),
        ),
        ButtonSegment<StyleSegment>(
          value: StyleSegment.widgetButtonStyle,
          label: Text('Widget Button Style'),
        ),
        ButtonSegment<StyleSegment>(
          value: StyleSegment.widgetStyleFrom,
          label: Text('Widget Style From'),
        ),
        ButtonSegment<StyleSegment>(
          value: StyleSegment.themeButtonStyle,
          label: Text('Theme Button Style'),
        ),
        ButtonSegment<StyleSegment>(
          value: StyleSegment.themeStyleFrom,
          label: Text('Theme Style From'),
        ),
      ],
      selected: <StyleSegment>{selectedSegment},
      onSelectionChanged: (Set<StyleSegment> newSelection) {
        onSegmentSelected(newSelection.first);
      },
    );
  }
}
```

</details>

### Preview

<img width="1175" alt="Screenshot 2024-11-12 at 12 10 43"
src="https://github.com/user-attachments/assets/a28207c5-0ef7-41fa-a45c-e9401df897a0">


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2024-12-03 21:19:12 +00:00

647 lines
27 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @docImport 'button_style_button.dart';
/// @docImport 'constants.dart';
/// @docImport 'elevated_button.dart';
/// @docImport 'elevated_button_theme.dart';
/// @docImport 'filled_button.dart';
/// @docImport 'filled_button_theme.dart';
/// @docImport 'material.dart';
/// @docImport 'no_splash.dart';
/// @docImport 'outlined_button.dart';
/// @docImport 'outlined_button_theme.dart';
/// @docImport 'text_button.dart';
/// @docImport 'text_button_theme.dart';
/// @docImport 'theme.dart';
library;
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button_style_button.dart';
import 'ink_well.dart';
import 'material_state.dart';
import 'theme_data.dart';
// Examples can assume:
// late BuildContext context;
// typedef MyAppHome = Placeholder;
/// The type for [ButtonStyle.backgroundBuilder] and [ButtonStyle.foregroundBuilder].
///
/// The [states] parameter is the button's current pressed/hovered/etc state. The [child] is
/// typically a descendant of the returned widget.
typedef ButtonLayerBuilder = Widget Function(BuildContext context, Set<MaterialState> states, Widget? child);
/// The visual properties that most buttons have in common.
///
/// Buttons and their themes have a ButtonStyle property which defines the visual
/// properties whose default values are to be overridden. The default values are
/// defined by the individual button widgets and are typically based on overall
/// theme's [ThemeData.colorScheme] and [ThemeData.textTheme].
///
/// All of the ButtonStyle properties are null by default.
///
/// Many of the ButtonStyle properties are [WidgetStateProperty] objects which
/// resolve to different values depending on the button's state. For example
/// the [Color] properties are defined with `WidgetStateProperty<Color>` and
/// can resolve to different colors depending on if the button is pressed,
/// hovered, focused, disabled, etc.
///
/// These properties can override the default value for just one state or all of
/// them. For example to create a [ElevatedButton] whose background color is the
/// color schemes primary color with 50% opacity, but only when the button is
/// pressed, one could write:
///
/// ```dart
/// ElevatedButton(
/// style: ButtonStyle(
/// backgroundColor: WidgetStateProperty.resolveWith<Color?>(
/// (Set<WidgetState> states) {
/// if (states.contains(WidgetState.pressed)) {
/// return Theme.of(context).colorScheme.primary.withOpacity(0.5);
/// }
/// return null; // Use the component's default.
/// },
/// ),
/// ),
/// child: const Text('Fly me to the moon'),
/// onPressed: () {
/// // ...
/// },
/// ),
/// ```
///
/// In this case the background color for all other button states would fallback
/// to the ElevatedButtons default values. To unconditionally set the button's
/// [backgroundColor] for all states one could write:
///
/// ```dart
/// ElevatedButton(
/// style: const ButtonStyle(
/// backgroundColor: WidgetStatePropertyAll<Color>(Colors.green),
/// ),
/// child: const Text('Let me play among the stars'),
/// onPressed: () {
/// // ...
/// },
/// ),
/// ```
///
/// Configuring a ButtonStyle directly makes it possible to very
/// precisely control the buttons visual attributes for all states.
/// This level of control is typically required when a custom
/// “branded” look and feel is desirable. However, in many cases its
/// useful to make relatively sweeping changes based on a few initial
/// parameters with simple values. The button styleFrom() methods
/// enable such sweeping changes. See for example:
/// [ElevatedButton.styleFrom], [FilledButton.styleFrom],
/// [OutlinedButton.styleFrom], [TextButton.styleFrom].
///
/// For example, to override the default text and icon colors for a
/// [TextButton], as well as its overlay color, with all of the
/// standard opacity adjustments for the pressed, focused, and
/// hovered states, one could write:
///
/// ```dart
/// TextButton(
/// style: TextButton.styleFrom(foregroundColor: Colors.green),
/// child: const Text('Let me see what spring is like'),
/// onPressed: () {
/// // ...
/// },
/// ),
/// ```
///
/// To configure all of the application's text buttons in the same
/// way, specify the overall theme's `textButtonTheme`:
///
/// ```dart
/// MaterialApp(
/// theme: ThemeData(
/// textButtonTheme: TextButtonThemeData(
/// style: TextButton.styleFrom(foregroundColor: Colors.green),
/// ),
/// ),
/// home: const MyAppHome(),
/// ),
/// ```
///
/// ## Material 3 button types
///
/// Material Design 3 specifies five types of common buttons. Flutter provides
/// support for these using the following button classes:
/// <style>table,td,th { border-collapse: collapse; padding: 0.45em; } td { border: 1px solid }</style>
///
/// | Type | Flutter implementation |
/// | :----------- | :---------------------- |
/// | Elevated | [ElevatedButton] |
/// | Filled | [FilledButton] |
/// | Filled Tonal | [FilledButton.tonal] |
/// | Outlined | [OutlinedButton] |
/// | Text | [TextButton] |
///
/// {@tool dartpad}
/// This sample shows how to create each of the Material 3 button types with Flutter.
///
/// ** See code in examples/api/lib/material/button_style/button_style.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [ElevatedButtonTheme], the theme for [ElevatedButton]s.
/// * [FilledButtonTheme], the theme for [FilledButton]s.
/// * [OutlinedButtonTheme], the theme for [OutlinedButton]s.
/// * [TextButtonTheme], the theme for [TextButton]s.
@immutable
class ButtonStyle with Diagnosticable {
/// Create a [ButtonStyle].
const ButtonStyle({
this.textStyle,
this.backgroundColor,
this.foregroundColor,
this.overlayColor,
this.shadowColor,
this.surfaceTintColor,
this.elevation,
this.padding,
this.minimumSize,
this.fixedSize,
this.maximumSize,
this.iconColor,
this.iconSize,
this.iconAlignment,
this.side,
this.shape,
this.mouseCursor,
this.visualDensity,
this.tapTargetSize,
this.animationDuration,
this.enableFeedback,
this.alignment,
this.splashFactory,
this.backgroundBuilder,
this.foregroundBuilder,
});
/// The style for a button's [Text] widget descendants.
///
/// The color of the [textStyle] is typically not used directly, the
/// [foregroundColor] is used instead.
final MaterialStateProperty<TextStyle?>? textStyle;
/// The button's background fill color.
final MaterialStateProperty<Color?>? backgroundColor;
/// The color for the button's [Text] widget descendants.
///
/// This color is typically used instead of the color of the [textStyle]. All
/// of the components that compute defaults from [ButtonStyle] values
/// compute a default [foregroundColor] and use that instead of the
/// [textStyle]'s color.
final MaterialStateProperty<Color?>? foregroundColor;
/// The highlight color that's typically used to indicate that
/// the button is focused, hovered, or pressed.
final MaterialStateProperty<Color?>? overlayColor;
/// The shadow color of the button's [Material].
///
/// The material's elevation shadow can be difficult to see for
/// dark themes, so by default the button classes add a
/// semi-transparent overlay to indicate elevation. See
/// [ThemeData.applyElevationOverlayColor].
final MaterialStateProperty<Color?>? shadowColor;
/// The surface tint color of the button's [Material].
///
/// See [Material.surfaceTintColor] for more details.
final MaterialStateProperty<Color?>? surfaceTintColor;
/// The elevation of the button's [Material].
final MaterialStateProperty<double?>? elevation;
/// The padding between the button's boundary and its child.
///
/// The vertical aspect of the default or user-specified padding is adjusted
/// automatically based on [visualDensity].
///
/// When the visual density is [VisualDensity.compact], the top and bottom insets
/// are reduced by 8 pixels or set to 0 pixels if the result of the reduced padding
/// is negative. For example: the visual density defaults to [VisualDensity.compact]
/// on desktop and web, so if the provided padding is 16 pixels on the top and bottom,
/// it will be reduced to 8 pixels on the top and bottom. If the provided padding
/// is 4 pixels, the result will be no padding on the top and bottom.
///
/// When the visual density is [VisualDensity.comfortable], the top and bottom insets
/// are reduced by 4 pixels or set to 0 pixels if the result of the reduced padding
/// is negative.
///
/// When the visual density is [VisualDensity.standard] the top and bottom insets
/// are not changed. The visual density defaults to [VisualDensity.standard] on mobile.
///
/// See [ThemeData.visualDensity] for more details.
final MaterialStateProperty<EdgeInsetsGeometry?>? padding;
/// The minimum size of the button itself.
///
/// The size of the rectangle the button lies within may be larger
/// per [tapTargetSize].
///
/// This value must be less than or equal to [maximumSize].
final MaterialStateProperty<Size?>? minimumSize;
/// The button's size.
///
/// This size is still constrained by the style's [minimumSize]
/// and [maximumSize]. Fixed size dimensions whose value is
/// [double.infinity] are ignored.
///
/// To specify buttons with a fixed width and the default height use
/// `fixedSize: Size.fromWidth(320)`. Similarly, to specify a fixed
/// height and the default width use `fixedSize: Size.fromHeight(100)`.
final MaterialStateProperty<Size?>? fixedSize;
/// The maximum size of the button itself.
///
/// A [Size.infinite] or null value for this property means that
/// the button's maximum size is not constrained.
///
/// This value must be greater than or equal to [minimumSize].
final MaterialStateProperty<Size?>? maximumSize;
/// The icon's color inside of the button.
final MaterialStateProperty<Color?>? iconColor;
/// The icon's size inside of the button.
final MaterialStateProperty<double?>? iconSize;
/// The alignment of the button's icon.
///
/// This property is supported for the following button types:
///
/// * [ElevatedButton.icon].
/// * [FilledButton.icon].
/// * [FilledButton.tonalIcon].
/// * [OutlinedButton.icon].
/// * [TextButton.icon].
///
/// See also:
///
/// * [IconAlignment], for more information about the different icon
/// alignments.
final IconAlignment? iconAlignment;
/// The color and weight of the button's outline.
///
/// This value is combined with [shape] to create a shape decorated
/// with an outline.
final MaterialStateProperty<BorderSide?>? side;
/// The shape of the button's underlying [Material].
///
/// This shape is combined with [side] to create a shape decorated
/// with an outline.
final MaterialStateProperty<OutlinedBorder?>? shape;
/// The cursor for a mouse pointer when it enters or is hovering over
/// this button's [InkWell].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// Defines how compact the button's layout will be.
///
/// {@macro flutter.material.themedata.visualDensity}
///
/// See also:
///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
/// within a [Theme].
final VisualDensity? visualDensity;
/// Configures the minimum size of the area within which the button may be pressed.
///
/// If the [tapTargetSize] is larger than [minimumSize], the button will include
/// a transparent margin that responds to taps.
///
/// Always defaults to [ThemeData.materialTapTargetSize].
final MaterialTapTargetSize? tapTargetSize;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// Typically the component default value is [kThemeChangeDuration].
final Duration? animationDuration;
/// Whether detected gestures should provide acoustic and/or haptic feedback.
///
/// For example, on Android a tap will produce a clicking sound and a
/// long-press will produce a short vibration, when feedback is enabled.
///
/// Typically the component default value is true.
///
/// See also:
///
/// * [Feedback] for providing platform-specific feedback to certain actions.
final bool? enableFeedback;
/// The alignment of the button's child.
///
/// Typically buttons are sized to be just big enough to contain the child and its
/// padding. If the button's size is constrained to a fixed size, for example by
/// enclosing it with a [SizedBox], this property defines how the child is aligned
/// within the available space.
///
/// Always defaults to [Alignment.center].
final AlignmentGeometry? alignment;
/// Creates the [InkWell] splash factory, which defines the appearance of
/// "ink" splashes that occur in response to taps.
///
/// Use [NoSplash.splashFactory] to defeat ink splash rendering. For example:
/// ```dart
/// ElevatedButton(
/// style: ElevatedButton.styleFrom(
/// splashFactory: NoSplash.splashFactory,
/// ),
/// onPressed: () { },
/// child: const Text('No Splash'),
/// )
/// ```
final InteractiveInkFeatureFactory? splashFactory;
/// Creates a widget that becomes the child of the button's [Material]
/// and whose child is the rest of the button, including the button's
/// `child` parameter.
///
/// The widget created by [backgroundBuilder] is constrained to be
/// the same size as the overall button and will appear behind the
/// button's child. The widget created by [foregroundBuilder] is
/// constrained to be the same size as the button's child, i.e. it's
/// inset by [ButtonStyle.padding] and aligned by the button's
/// [ButtonStyle.alignment].
///
/// By default the returned widget is clipped to the Material's [ButtonStyle.shape].
///
/// See also:
///
/// * [foregroundBuilder], to create a widget that's as big as the button's
/// child and is layered behind the child.
/// * [ButtonStyleButton.clipBehavior], for more information about
/// configuring clipping.
final ButtonLayerBuilder? backgroundBuilder;
/// Creates a Widget that contains the button's child parameter which is used
/// instead of the button's child.
///
/// The returned widget is clipped by the button's
/// [ButtonStyle.shape], inset by the button's [ButtonStyle.padding]
/// and aligned by the button's [ButtonStyle.alignment].
///
/// See also:
///
/// * [backgroundBuilder], to create a widget that's as big as the button and
/// is layered behind the button's child.
/// * [ButtonStyleButton.clipBehavior], for more information about
/// configuring clipping.
final ButtonLayerBuilder? foregroundBuilder;
/// Returns a copy of this ButtonStyle with the given fields replaced with
/// the new values.
ButtonStyle copyWith({
MaterialStateProperty<TextStyle?>? textStyle,
MaterialStateProperty<Color?>? backgroundColor,
MaterialStateProperty<Color?>? foregroundColor,
MaterialStateProperty<Color?>? overlayColor,
MaterialStateProperty<Color?>? shadowColor,
MaterialStateProperty<Color?>? surfaceTintColor,
MaterialStateProperty<double?>? elevation,
MaterialStateProperty<EdgeInsetsGeometry?>? padding,
MaterialStateProperty<Size?>? minimumSize,
MaterialStateProperty<Size?>? fixedSize,
MaterialStateProperty<Size?>? maximumSize,
MaterialStateProperty<Color?>? iconColor,
MaterialStateProperty<double?>? iconSize,
IconAlignment? iconAlignment,
MaterialStateProperty<BorderSide?>? side,
MaterialStateProperty<OutlinedBorder?>? shape,
MaterialStateProperty<MouseCursor?>? mouseCursor,
VisualDensity? visualDensity,
MaterialTapTargetSize? tapTargetSize,
Duration? animationDuration,
bool? enableFeedback,
AlignmentGeometry? alignment,
InteractiveInkFeatureFactory? splashFactory,
ButtonLayerBuilder? backgroundBuilder,
ButtonLayerBuilder? foregroundBuilder,
}) {
return ButtonStyle(
textStyle: textStyle ?? this.textStyle,
backgroundColor: backgroundColor ?? this.backgroundColor,
foregroundColor: foregroundColor ?? this.foregroundColor,
overlayColor: overlayColor ?? this.overlayColor,
shadowColor: shadowColor ?? this.shadowColor,
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
elevation: elevation ?? this.elevation,
padding: padding ?? this.padding,
minimumSize: minimumSize ?? this.minimumSize,
fixedSize: fixedSize ?? this.fixedSize,
maximumSize: maximumSize ?? this.maximumSize,
iconColor: iconColor ?? this.iconColor,
iconSize: iconSize ?? this.iconSize,
iconAlignment: iconAlignment ?? this.iconAlignment,
side: side ?? this.side,
shape: shape ?? this.shape,
mouseCursor: mouseCursor ?? this.mouseCursor,
visualDensity: visualDensity ?? this.visualDensity,
tapTargetSize: tapTargetSize ?? this.tapTargetSize,
animationDuration: animationDuration ?? this.animationDuration,
enableFeedback: enableFeedback ?? this.enableFeedback,
alignment: alignment ?? this.alignment,
splashFactory: splashFactory ?? this.splashFactory,
backgroundBuilder: backgroundBuilder ?? this.backgroundBuilder,
foregroundBuilder: foregroundBuilder ?? this.foregroundBuilder,
);
}
/// Returns a copy of this ButtonStyle where the non-null fields in [style]
/// have replaced the corresponding null fields in this ButtonStyle.
///
/// In other words, [style] is used to fill in unspecified (null) fields
/// this ButtonStyle.
ButtonStyle merge(ButtonStyle? style) {
if (style == null) {
return this;
}
return copyWith(
textStyle: textStyle ?? style.textStyle,
backgroundColor: backgroundColor ?? style.backgroundColor,
foregroundColor: foregroundColor ?? style.foregroundColor,
overlayColor: overlayColor ?? style.overlayColor,
shadowColor: shadowColor ?? style.shadowColor,
surfaceTintColor: surfaceTintColor ?? style.surfaceTintColor,
elevation: elevation ?? style.elevation,
padding: padding ?? style.padding,
minimumSize: minimumSize ?? style.minimumSize,
fixedSize: fixedSize ?? style.fixedSize,
maximumSize: maximumSize ?? style.maximumSize,
iconColor: iconColor ?? style.iconColor,
iconSize: iconSize ?? style.iconSize,
iconAlignment: iconAlignment ?? style.iconAlignment,
side: side ?? style.side,
shape: shape ?? style.shape,
mouseCursor: mouseCursor ?? style.mouseCursor,
visualDensity: visualDensity ?? style.visualDensity,
tapTargetSize: tapTargetSize ?? style.tapTargetSize,
animationDuration: animationDuration ?? style.animationDuration,
enableFeedback: enableFeedback ?? style.enableFeedback,
alignment: alignment ?? style.alignment,
splashFactory: splashFactory ?? style.splashFactory,
backgroundBuilder: backgroundBuilder ?? style.backgroundBuilder,
foregroundBuilder: foregroundBuilder ?? style.foregroundBuilder,
);
}
@override
int get hashCode {
final List<Object?> values = <Object?>[
textStyle,
backgroundColor,
foregroundColor,
overlayColor,
shadowColor,
surfaceTintColor,
elevation,
padding,
minimumSize,
fixedSize,
maximumSize,
iconColor,
iconSize,
iconAlignment,
side,
shape,
mouseCursor,
visualDensity,
tapTargetSize,
animationDuration,
enableFeedback,
alignment,
splashFactory,
backgroundBuilder,
foregroundBuilder,
];
return Object.hashAll(values);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is ButtonStyle
&& other.textStyle == textStyle
&& other.backgroundColor == backgroundColor
&& other.foregroundColor == foregroundColor
&& other.overlayColor == overlayColor
&& other.shadowColor == shadowColor
&& other.surfaceTintColor == surfaceTintColor
&& other.elevation == elevation
&& other.padding == padding
&& other.minimumSize == minimumSize
&& other.fixedSize == fixedSize
&& other.maximumSize == maximumSize
&& other.iconColor == iconColor
&& other.iconSize == iconSize
&& other.iconAlignment == iconAlignment
&& other.side == side
&& other.shape == shape
&& other.mouseCursor == mouseCursor
&& other.visualDensity == visualDensity
&& other.tapTargetSize == tapTargetSize
&& other.animationDuration == animationDuration
&& other.enableFeedback == enableFeedback
&& other.alignment == alignment
&& other.splashFactory == splashFactory
&& other.backgroundBuilder == backgroundBuilder
&& other.foregroundBuilder == foregroundBuilder;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('backgroundColor', backgroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('foregroundColor', foregroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('shadowColor', shadowColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('surfaceTintColor', surfaceTintColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<EdgeInsetsGeometry?>>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('minimumSize', minimumSize, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('fixedSize', fixedSize, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('maximumSize', maximumSize, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('iconColor', iconColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('iconSize', iconSize, defaultValue: null));
properties.add(EnumProperty<IconAlignment>('iconAlignment', iconAlignment, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
properties.add(EnumProperty<MaterialTapTargetSize>('tapTargetSize', tapTargetSize, defaultValue: null));
properties.add(DiagnosticsProperty<Duration>('animationDuration', animationDuration, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
properties.add(DiagnosticsProperty<ButtonLayerBuilder>('backgroundBuilder', backgroundBuilder, defaultValue: null));
properties.add(DiagnosticsProperty<ButtonLayerBuilder>('foregroundBuilder', foregroundBuilder, defaultValue: null));
}
/// Linearly interpolate between two [ButtonStyle]s.
static ButtonStyle? lerp(ButtonStyle? a, ButtonStyle? b, double t) {
if (identical(a, b)) {
return a;
}
return ButtonStyle(
textStyle: MaterialStateProperty.lerp<TextStyle?>(a?.textStyle, b?.textStyle, t, TextStyle.lerp),
backgroundColor: MaterialStateProperty.lerp<Color?>(a?.backgroundColor, b?.backgroundColor, t, Color.lerp),
foregroundColor: MaterialStateProperty.lerp<Color?>(a?.foregroundColor, b?.foregroundColor, t, Color.lerp),
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
shadowColor: MaterialStateProperty.lerp<Color?>(a?.shadowColor, b?.shadowColor, t, Color.lerp),
surfaceTintColor: MaterialStateProperty.lerp<Color?>(a?.surfaceTintColor, b?.surfaceTintColor, t, Color.lerp),
elevation: MaterialStateProperty.lerp<double?>(a?.elevation, b?.elevation, t, lerpDouble),
padding: MaterialStateProperty.lerp<EdgeInsetsGeometry?>(a?.padding, b?.padding, t, EdgeInsetsGeometry.lerp),
minimumSize: MaterialStateProperty.lerp<Size?>(a?.minimumSize, b?.minimumSize, t, Size.lerp),
fixedSize: MaterialStateProperty.lerp<Size?>(a?.fixedSize, b?.fixedSize, t, Size.lerp),
maximumSize: MaterialStateProperty.lerp<Size?>(a?.maximumSize, b?.maximumSize, t, Size.lerp),
iconColor: MaterialStateProperty.lerp<Color?>(a?.iconColor, b?.iconColor, t, Color.lerp),
iconSize: MaterialStateProperty.lerp<double?>(a?.iconSize, b?.iconSize, t, lerpDouble),
iconAlignment: t < 0.5 ? a?.iconAlignment : b?.iconAlignment,
side: _lerpSides(a?.side, b?.side, t),
shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
tapTargetSize: t < 0.5 ? a?.tapTargetSize : b?.tapTargetSize,
animationDuration: t < 0.5 ? a?.animationDuration : b?.animationDuration,
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
splashFactory: t < 0.5 ? a?.splashFactory : b?.splashFactory,
backgroundBuilder: t < 0.5 ? a?.backgroundBuilder : b?.backgroundBuilder,
foregroundBuilder: t < 0.5 ? a?.foregroundBuilder : b?.foregroundBuilder,
);
}
// Special case because BorderSide.lerp() doesn't support null arguments
static MaterialStateProperty<BorderSide?>? _lerpSides(MaterialStateProperty<BorderSide?>? a, MaterialStateProperty<BorderSide?>? b, double t) {
if (a == null && b == null) {
return null;
}
return MaterialStateBorderSide.lerp(a, b, t);
}
}