button_theme.dart 35.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

8 9 10 11 12
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'flat_button.dart';
import 'material_button.dart';
13
import 'material_state.dart';
14 15
import 'outline_button.dart';
import 'raised_button.dart';
16
import 'theme.dart';
17
import 'theme_data.dart' show MaterialTapTargetSize;
18 19 20 21 22 23 24

/// Used with [ButtonTheme] and [ButtonThemeData] to define a button's base
/// colors, and the defaults for the button's minimum size, internal padding,
/// and shape.
///
/// See also:
///
25 26
///  * [RaisedButton], [FlatButton], [OutlineButton], which are configured
///    based on the ambient [ButtonTheme].
27 28 29 30 31 32 33 34 35 36 37
enum ButtonTextTheme {
  /// Button text is black or white depending on [ThemeData.brightness].
  normal,

  /// Button text is [ThemeData.accentColor].
  accent,

  /// Button text is based on [ThemeData.primaryColor].
  primary,
}

38 39 40 41 42 43 44 45 46 47 48 49 50
/// Used with [ButtonTheme] and [ButtonThemeData] to define how the button bar
/// should size itself with either constraints or internal padding.
enum ButtonBarLayoutBehavior {
  /// Button bars will be constrained to a minimum height of 52.
  ///
  /// This setting is require to create button bars which conform to the
  /// material specification.
  constrained,

  /// Button bars will calculate their padding from the button theme padding.
  padded,
}

51 52
/// Used with [ButtonThemeData] to configure the color and geometry of buttons.
///
53 54 55 56 57 58 59 60 61 62
/// ### This class is obsolete.
///
/// Please use one or more of the new buttons and their themes
/// instead: [TextButton] and [TextButtonTheme], [ElevatedButton] and
/// [ElevatedButtonTheme], [OutlinedButton] and
/// [OutlinedButtonTheme]. The original classes will be deprecated
/// soon, please migrate code that uses them.  There's a detailed
/// migration guide for the new button and button theme classes in
/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
///
63
/// A button theme can be specified as part of the overall Material theme
Josh Soref's avatar
Josh Soref committed
64
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
65 66 67
/// can be overridden with [ButtonTheme].
///
/// The actual appearance of buttons depends on the button theme, the
68
/// button's enabled state, its elevation (if any), and the overall [Theme].
69 70 71
///
/// See also:
///
72 73
///  * [FlatButton] [RaisedButton], and [OutlineButton], which are styled
///    based on the ambient button theme.
74 75
///  * [RawMaterialButton], which can be used to configure a button that doesn't
///    depend on any inherited themes.
76
class ButtonTheme extends InheritedTheme {
77 78
  /// Creates a button theme.
  ///
79 80
  /// The [textTheme], [minWidth], [height], and [colorScheme] arguments
  /// must not be null.
81
  ButtonTheme({
82
    Key? key,
83
    ButtonTextTheme textTheme = ButtonTextTheme.normal,
84
    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
85 86
    double minWidth = 88.0,
    double height = 36.0,
87 88
    EdgeInsetsGeometry? padding,
    ShapeBorder? shape,
89
    bool alignedDropdown = false,
90 91 92 93 94 95 96 97 98
    Color? buttonColor,
    Color? disabledColor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    Color? splashColor,
    ColorScheme? colorScheme,
    MaterialTapTargetSize? materialTapTargetSize,
    required Widget child,
99 100 101
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
102
       assert(alignedDropdown != null),
103
       assert(layoutBehavior != null),
104
       data = ButtonThemeData(
105 106 107 108 109
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
110 111
         alignedDropdown: alignedDropdown,
         layoutBehavior: layoutBehavior,
112 113
         buttonColor: buttonColor,
         disabledColor: disabledColor,
114 115
         focusColor: focusColor,
         hoverColor: hoverColor,
116 117 118 119
         highlightColor: highlightColor,
         splashColor: splashColor,
         colorScheme: colorScheme,
         materialTapTargetSize: materialTapTargetSize,
120 121 122
       ),
       super(key: key, child: child);

123 124 125 126
  /// Creates a button theme from [data].
  ///
  /// The [data] argument must not be null.
  const ButtonTheme.fromButtonThemeData({
127 128 129
    Key? key,
    required this.data,
    required Widget child,
130 131 132
  }) : assert(data != null),
       super(key: key, child: child);

133 134 135
  /// Creates a button theme that is appropriate for button bars, as used in
  /// dialog footers and in the headers of data tables.
  ///
136 137
  /// Deprecated. Please use [ButtonBarTheme] instead which offers more
  /// flexibility to configure [ButtonBar] widgets.
138
  ///
139
  /// To migrate instances of code that were just wrapping a [ButtonBar]:
140
  ///
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
  /// ```dart
  /// ButtonTheme.bar(
  ///   child: ButtonBar(...)
  /// );
  /// ```
  ///
  /// you can just remove the `ButtonTheme.bar` as the defaults are now handled
  /// by [ButtonBar] directly.
  ///
  /// If you have more complicated usages of `ButtonTheme.bar` like:
  ///
  /// ```dart
  /// ButtonTheme.bar(
  ///   padding: EdgeInsets.symmetric(horizontal: 10.0),
  ///   textTheme: ButtonTextTheme.accent,
  ///   child: ButtonBar(...),
  /// );
  /// ```
  ///
  /// you can remove the `ButtonTheme.bar` and move the parameters to the
  /// [ButtonBar] instance directly:
  ///
  /// ```dart
  /// ButtonBar(
  ///   padding: EdgeInsets.symmetric(horizontal: 10.0),
  ///   textTheme: ButtonTextTheme.accent,
  ///   ...
  /// );
  /// ```
  ///
  /// You can also replace the defaults for all [ButtonBar] widgets by updating
  /// [ThemeData.buttonBarTheme] for your app.
173 174 175 176
  @Deprecated(
    'Use ButtonBarTheme instead. '
    'This feature was deprecated after v1.9.1.'
  )
177
  ButtonTheme.bar({
178
    Key? key,
179 180 181 182
    ButtonTextTheme textTheme = ButtonTextTheme.accent,
    double minWidth = 64.0,
    double height = 36.0,
    EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0),
183
    ShapeBorder? shape,
184
    bool alignedDropdown = false,
185 186 187 188 189 190 191 192
    Color? buttonColor,
    Color? disabledColor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    Color? splashColor,
    ColorScheme? colorScheme,
    required Widget child,
193
    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
194 195 196
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
197
       assert(alignedDropdown != null),
198
       data = ButtonThemeData(
199 200 201 202 203
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
204
         alignedDropdown: alignedDropdown,
205
         layoutBehavior: layoutBehavior,
206 207
         buttonColor: buttonColor,
         disabledColor: disabledColor,
208 209
         focusColor: focusColor,
         hoverColor: hoverColor,
210 211 212
         highlightColor: highlightColor,
         splashColor: splashColor,
         colorScheme: colorScheme,
213 214 215 216 217 218 219 220 221 222 223 224 225 226
       ),
       super(key: key, child: child);

  /// Specifies the color and geometry of buttons.
  final ButtonThemeData data;

  /// The closest instance of this class that encloses the given context.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// ButtonThemeData theme = ButtonTheme.of(context);
  /// ```
  static ButtonThemeData of(BuildContext context) {
227 228
    final ButtonTheme? inheritedButtonTheme = context.dependOnInheritedWidgetOfExactType<ButtonTheme>();
    ButtonThemeData? buttonTheme = inheritedButtonTheme?.data;
229
    if (buttonTheme?.colorScheme == null) { // if buttonTheme or buttonTheme.colorScheme is null
230
      final ThemeData theme = Theme.of(context)!;
231
      buttonTheme ??= theme.buttonTheme;
232 233 234 235 236 237
      if (buttonTheme.colorScheme == null) {
        buttonTheme = buttonTheme.copyWith(
          colorScheme: theme.buttonTheme.colorScheme ?? theme.colorScheme,
        );
        assert(buttonTheme.colorScheme != null);
      }
238
    }
239
    return buttonTheme!;
240 241
  }

242 243
  @override
  Widget wrap(BuildContext context, Widget child) {
244
    final ButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ButtonTheme>();
245 246 247
    return identical(this, ancestorTheme) ? child : ButtonTheme.fromButtonThemeData(data: data, child: child);
  }

248
  @override
249
  bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
250 251 252 253
}

/// Used with [ButtonTheme] to configure the color and geometry of buttons.
///
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
/// ### This class is obsolete.
///
/// Please use one or more of the new buttons and their themes instead:
///
///  * [TextButton], [TextButtonTheme], [TextButtonThemeData],
///  * [ElevatedButton], [ElevatedButtonTheme], [ElevatedButtonThemeData],
///  * [OutlinedButton], [OutlinedButtonTheme], [OutlinedButtonThemeData]
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// TextButton, ElevatedButton, and OutlinedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme,
/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
/// will be deprecated soon, please migrate code that uses them.
/// There's a detailed migration guide for the new button and button
/// theme classes in
/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
///
271
/// A button theme can be specified as part of the overall Material theme
Josh Soref's avatar
Josh Soref committed
272
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
273
/// can be overridden with [ButtonTheme].
274
@immutable
275
class ButtonThemeData with Diagnosticable {
276 277 278
  /// Create a button theme object that can be used with [ButtonTheme]
  /// or [ThemeData].
  ///
279
  /// The [textTheme], [minWidth], [height], [alignedDropdown], and
280 281 282 283 284 285
  /// [layoutBehavior] parameters must not be null. The [minWidth] and
  /// [height] parameters must greater than or equal to zero.
  ///
  /// The ButtonTheme's methods that have a [MaterialButton] parameter and
  /// have a name with a `get` prefix are used by [RaisedButton],
  /// [OutlineButton], and [FlatButton] to configure a [RawMaterialButton].
286
  const ButtonThemeData({
287 288 289
    this.textTheme = ButtonTextTheme.normal,
    this.minWidth = 88.0,
    this.height = 36.0,
290 291
    EdgeInsetsGeometry? padding,
    ShapeBorder? shape,
292
    this.layoutBehavior = ButtonBarLayoutBehavior.padded,
293
    this.alignedDropdown = false,
294 295 296 297 298 299
    Color? buttonColor,
    Color? disabledColor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    Color? splashColor,
300
    this.colorScheme,
301
    MaterialTapTargetSize? materialTapTargetSize,
302 303 304
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
305
       assert(alignedDropdown != null),
306
       assert(layoutBehavior != null),
307 308
       _buttonColor = buttonColor,
       _disabledColor = disabledColor,
309 310
       _focusColor = focusColor,
       _hoverColor = hoverColor,
311 312
       _highlightColor = highlightColor,
       _splashColor = splashColor,
313
       _padding = padding,
314 315
       _shape = shape,
       _materialTapTargetSize = materialTapTargetSize;
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331

  /// The minimum width for buttons.
  ///
  /// The actual horizontal space allocated for a button's child is
  /// at least this value less the theme's horizontal [padding].
  ///
  /// Defaults to 88.0 logical pixels.
  final double minWidth;

  /// The minimum height for buttons.
  ///
  /// Defaults to 36.0 logical pixels.
  final double height;

  /// Defines a button's base colors, and the defaults for the button's minimum
  /// size, internal padding, and shape.
332 333 334
  ///
  /// Despite the name, this property is not a [TextTheme], its value is not a
  /// collection of [TextStyle]s.
335 336
  final ButtonTextTheme textTheme;

337 338
  /// Defines whether a [ButtonBar] should size itself with a minimum size
  /// constraint or with padding.
339 340 341 342
  ///
  /// Defaults to [ButtonBarLayoutBehavior.padded].
  final ButtonBarLayoutBehavior layoutBehavior;

343 344
  /// Simply a convenience that returns [minWidth] and [height] as a
  /// [BoxConstraints] object:
345
  ///
346
  /// ```dart
347
  /// return BoxConstraints(
348
  ///   minWidth: minWidth,
349
  ///   minHeight: height,
350 351 352
  /// );
  /// ```
  BoxConstraints get constraints {
353
    return BoxConstraints(
354 355 356 357 358 359 360 361 362
      minWidth: minWidth,
      minHeight: height,
    );
  }

  /// Padding for a button's child (typically the button's label).
  ///
  /// Defaults to 24.0 on the left and right if [textTheme] is
  /// [ButtonTextTheme.primary], 16.0 on the left and right otherwise.
363 364 365 366 367
  ///
  /// See also:
  ///
  ///  * [getPadding], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
368 369
  EdgeInsetsGeometry get padding {
    if (_padding != null)
370
      return _padding!;
371
    switch (textTheme) {
372 373 374 375
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const EdgeInsets.symmetric(horizontal: 16.0);
      case ButtonTextTheme.primary:
376
        return const EdgeInsets.symmetric(horizontal: 24.0);
377 378
    }
  }
379
  final EdgeInsetsGeometry? _padding;
380 381 382 383 384 385 386 387 388 389

  /// The shape of a button's material.
  ///
  /// The button's highlight and splash are clipped to this shape. If the
  /// button has an elevation, then its drop shadow is defined by this
  /// shape as well.
  ///
  /// Defaults to a rounded rectangle with circular corner radii of 4.0 if
  /// [textTheme] is [ButtonTextTheme.primary], a rounded rectangle with
  /// circular corner radii of 2.0 otherwise.
390 391 392 393 394
  ///
  /// See also:
  ///
  ///  * [getShape], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
395 396
  ShapeBorder get shape {
    if (_shape != null)
397
      return _shape!;
398 399 400 401
    switch (textTheme) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const RoundedRectangleBorder(
402
          borderRadius: BorderRadius.all(Radius.circular(2.0)),
403 404 405
        );
      case ButtonTextTheme.primary:
        return const RoundedRectangleBorder(
406
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
407 408 409
        );
    }
  }
410
  final ShapeBorder? _shape;
411

412 413 414 415 416 417 418 419 420 421 422
  /// If true, then a [DropdownButton] menu's width will match the button's
  /// width.
  ///
  /// If false (the default), then the dropdown's menu will be wider than
  /// its button. In either case the dropdown button will line up the leading
  /// edge of the menu's value with the leading edge of the values
  /// displayed by the menu items.
  ///
  /// This property only affects [DropdownButton] and its menu.
  final bool alignedDropdown;

423 424 425 426
  /// The background fill color for [RaisedButton]s.
  ///
  /// This property is null by default.
  ///
427 428
  /// If the button is in the focused, hovering, or highlighted state, then the
  /// [focusColor], [hoverColor], or [highlightColor] will take precedence over
429
  /// the [buttonColor].
430
  ///
431 432 433 434
  /// See also:
  ///
  ///  * [getFillColor], which is used by [RaisedButton] to compute its
  ///    background fill color.
435
  final Color? _buttonColor;
436 437 438 439 440 441 442 443 444

  /// The background fill color for disabled [RaisedButton]s.
  ///
  /// This property is null by default.
  ///
  /// See also:
  ///
  ///  * [getDisabledFillColor], which is used by [RaisedButton] to compute its
  ///    background fill color.
445
  final Color? _disabledColor;
446

447 448 449 450 451 452 453 454 455 456 457
  /// The fill color of the button when it has the input focus.
  ///
  /// This property is null by default.
  ///
  /// If the button is in the hovering or highlighted state, then the [hoverColor]
  /// or [highlightColor] will take precedence over the [focusColor].
  ///
  /// See also:
  ///
  ///  * [getFocusColor], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
458
  final Color? _focusColor;
459 460 461 462 463 464 465 466 467 468 469 470

  /// The fill color of the button when a pointer is hovering over it.
  ///
  /// This property is null by default.
  ///
  /// If the button is in the highlighted state, then the [highlightColor] will
  /// take precedence over the [hoverColor].
  ///
  /// See also:
  ///
  ///  * [getHoverColor], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
471
  final Color? _hoverColor;
472

473 474 475 476 477 478 479 480
  /// The color of the overlay that appears when a button is pressed.
  ///
  /// This property is null by default.
  ///
  /// See also:
  ///
  ///  * [getHighlightColor], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
481
  final Color? _highlightColor;
482 483 484 485 486 487 488 489 490

  /// The color of the ink "splash" overlay that appears when a button is tapped.
  ///
  /// This property is null by default.
  ///
  /// See also:
  ///
  ///  * [getSplashColor], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
491
  final Color? _splashColor;
492 493 494 495 496 497 498 499 500 501 502

  /// A set of thirteen colors that can be used to derive the button theme's
  /// colors.
  ///
  /// This property was added much later than the theme's set of highly
  /// specific colors, like [ThemeData.buttonColor], [ThemeData.highlightColor],
  /// [ThemeData.splashColor] etc.
  ///
  /// The colors for new button classes can be defined exclusively in terms
  /// of [colorScheme]. When it's possible, the existing buttons will
  /// (continue to) gradually migrate to it.
503
  final ColorScheme? colorScheme;
504 505 506 507 508 509 510 511 512

  // The minimum size of a button's tap target.
  //
  // This property is null by default.
  //
  // See also:
  //
  //  * [getMaterialTargetTapSize], which is used by [RaisedButton],
  //    [OutlineButton] and [FlatButton].
513
  final MaterialTapTargetSize? _materialTapTargetSize;
514 515 516 517 518 519

  /// The [button]'s overall brightness.
  ///
  /// Returns the button's [MaterialButton.colorBrightness] if it is non-null,
  /// otherwise the color scheme's [ColorScheme.brightness] is returned.
  Brightness getBrightness(MaterialButton button) {
520
    return button.colorBrightness ?? colorScheme!.brightness;
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
  }

  /// Defines the [button]'s base colors, and the defaults for the button's
  /// minimum size, internal padding, and shape.
  ///
  /// Despite the name, this property is not the [TextTheme] whose
  /// [TextTheme.button] is used as the button text's [TextStyle].
  ButtonTextTheme getTextTheme(MaterialButton button) {
    return button.textTheme ?? textTheme;
  }

  /// The foreground color of the [button]'s text and icon when
  /// [MaterialButton.onPressed] is null (when MaterialButton.enabled is false).
  ///
  /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
  /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
537
  /// with its opacity set to 0.38.
538
  ///
539 540
  /// If [MaterialButton.textColor] is a [MaterialStateProperty<Color>], it will be
  /// used as the `disabledTextColor`. It will be resolved in the [MaterialState.disabled] state.
541
  Color getDisabledTextColor(MaterialButton button) {
542
    if (button.textColor is MaterialStateProperty<Color?>)
543
      return button.textColor!;
544
    if (button.disabledTextColor != null)
545 546
      return button.disabledTextColor!;
    return colorScheme!.onSurface.withOpacity(0.38);
547 548 549
  }

  /// The [button]'s background color when [MaterialButton.onPressed] is null
550
  /// (when [MaterialButton.enabled] is false).
551 552 553
  ///
  /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
  ///
554
  /// Otherwise the value of the `disabledColor` constructor parameter
555 556 557
  /// is returned, if it is non-null.
  ///
  /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
558
  /// with its opacity set to 0.38.
559 560
  Color getDisabledFillColor(MaterialButton button) {
    if (button.disabledColor != null)
561
      return button.disabledColor!;
562
    if (_disabledColor != null)
563 564
      return _disabledColor!;
    return colorScheme!.onSurface.withOpacity(0.38);
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
  }

  /// The button's background fill color or null for buttons that don't have
  /// a background color.
  ///
  /// Returns [MaterialButton.color] if it is non-null and the button
  /// is enabled.
  ///
  /// Otherwise, returns [MaterialButton.disabledColor] if it is non-null and
  /// the button is disabled.
  ///
  /// Otherwise, if button is a [FlatButton] or an [OutlineButton] then null is
  /// returned.
  ///
  /// Otherwise, if button is a [RaisedButton], returns the `buttonColor`
  /// constructor parameter if it was non-null and the button is enabled.
  ///
  /// Otherwise the fill color depends on the value of [getTextTheme].
  ///
  ///  * [ButtonTextTheme.normal] or [ButtonTextTheme.accent], the
  ///    color scheme's [ColorScheme.primary] color if the [button] is enabled
  ///    the value of [getDisabledFillColor] otherwise.
  ///  * [ButtonTextTheme.primary], if the [button] is enabled then the value
  ///    of the `buttonColor` constructor parameter if it is non-null,
  ///    otherwise the color scheme's ColorScheme.primary color. If the button
  ///    is not enabled then the colorScheme's [ColorScheme.onSurface] color
  ///    with opacity 0.12.
592 593
  Color? getFillColor(MaterialButton button) {
    final Color? fillColor = button.enabled ? button.color : button.disabledColor;
594 595 596
    if (fillColor != null)
      return fillColor;

597
    if (button is FlatButton || button is OutlineButton || button.runtimeType == MaterialButton)
598 599 600 601 602 603 604 605
      return null;

    if (button.enabled && button is RaisedButton && _buttonColor != null)
      return _buttonColor;

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
606
        return button.enabled ? colorScheme!.primary : getDisabledFillColor(button);
607 608
      case ButtonTextTheme.primary:
        return button.enabled
609 610
          ? _buttonColor ?? colorScheme!.primary
          : colorScheme!.onSurface.withOpacity(0.12);
611 612 613 614 615 616 617
    }
  }

  /// The foreground color of the [button]'s text and icon.
  ///
  /// If [button] is not [MaterialButton.enabled], the value of
  /// [getDisabledTextColor] is returned. If the button is enabled and
618 619
  /// [MaterialButton.textColor] is non-null, then [MaterialButton.textColor]
  /// is returned.
620 621 622 623
  ///
  /// Otherwise the text color depends on the value of [getTextTheme]
  /// and [getBrightness].
  ///
624 625 626
  ///  * [ButtonTextTheme.normal]: [Colors.white] is used if [getBrightness]
  ///    resolves to [Brightness.dark]. [Colors.black87] is used if
  ///    [getBrightness] resolves to [Brightness.light].
627
  ///  * [ButtonTextTheme.accent]: [ColorScheme.secondary] of [colorScheme].
628
  ///  * [ButtonTextTheme.primary]: If [getFillColor] is dark then [Colors.white],
629
  ///    otherwise if [button] is a [FlatButton] or an [OutlineButton] then
630
  ///    [ColorScheme.primary] of [colorScheme], otherwise [Colors.black].
631 632 633 634 635
  Color getTextColor(MaterialButton button) {
    if (!button.enabled)
      return getDisabledTextColor(button);

    if (button.textColor != null)
636
      return button.textColor!;
637 638 639 640 641 642

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
        return getBrightness(button) == Brightness.dark ? Colors.white : Colors.black87;

      case ButtonTextTheme.accent:
643
        return colorScheme!.secondary;
644

645
      case ButtonTextTheme.primary:
646
        final Color? fillColor = getFillColor(button);
647 648 649 650 651 652
        final bool fillIsDark = fillColor != null
          ? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
          : getBrightness(button) == Brightness.dark;
        if (fillIsDark)
          return Colors.white;
        if (button is FlatButton || button is OutlineButton)
653
          return colorScheme!.primary;
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
        return Colors.black;
    }
  }

  /// The color of the ink "splash" overlay that appears when the (enabled)
  /// [button] is tapped.
  ///
  /// Returns the button's [MaterialButton.splashColor] if it is non-null.
  ///
  /// Otherwise, returns the value of the `splashColor` constructor parameter
  /// it is non-null and [button] is a [RaisedButton] or an [OutlineButton].
  ///
  /// Otherwise, returns the value of the `splashColor` constructor parameter
  /// if it is non-null and [button] is a [FlatButton] and
  /// [getTextTheme] is not [ButtonTextTheme.primary]
  ///
  /// Otherwise, returns [getTextColor] with an opacity of 0.12.
  Color getSplashColor(MaterialButton button) {
    if (button.splashColor != null)
673
      return button.splashColor!;
674 675

    if (_splashColor != null && (button is RaisedButton || button is OutlineButton))
676
      return _splashColor!;
677 678 679 680 681

    if (_splashColor != null && button is FlatButton) {
      switch (getTextTheme(button)) {
        case ButtonTextTheme.normal:
        case ButtonTextTheme.accent:
682
          return _splashColor!;
683 684 685 686 687 688 689 690
        case ButtonTextTheme.primary:
          break;
      }
    }

    return getTextColor(button).withOpacity(0.12);
  }

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
  /// The fill color of the button when it has input focus.
  ///
  /// Returns the button's [MaterialButton.focusColor] if it is non-null.
  /// Otherwise the focus color depends on [getTextTheme]:
  ///
  ///  * [ButtonTextTheme.normal], [ButtonTextTheme.accent]: returns the
  ///    value of the `focusColor` constructor parameter if it is non-null,
  ///    otherwise the value of [getTextColor] with opacity 0.12.
  ///  * [ButtonTextTheme.primary], returns [Colors.transparent].
  Color getFocusColor(MaterialButton button) {
    return button.focusColor ?? _focusColor ?? getTextColor(button).withOpacity(0.12);
  }

  /// The fill color of the button when it has input focus.
  ///
  /// Returns the button's [MaterialButton.focusColor] if it is non-null.
  /// Otherwise the focus color depends on [getTextTheme]:
  ///
  ///  * [ButtonTextTheme.normal], [ButtonTextTheme.accent],
  ///    [ButtonTextTheme.primary]: returns the value of the `focusColor`
  ///    constructor parameter if it is non-null, otherwise the value of
  ///    [getTextColor] with opacity 0.04.
  Color getHoverColor(MaterialButton button) {
    return button.hoverColor ?? _hoverColor ?? getTextColor(button).withOpacity(0.04);
  }

717 718 719 720 721 722 723 724 725 726 727
  /// The color of the overlay that appears when the [button] is pressed.
  ///
  /// Returns the button's [MaterialButton.highlightColor] if it is non-null.
  /// Otherwise the highlight color depends on [getTextTheme]:
  ///
  ///  * [ButtonTextTheme.normal], [ButtonTextTheme.accent]: returns the
  ///    value of the `highlightColor` constructor parameter if it is non-null,
  ///    otherwise the value of [getTextColor] with opacity 0.16.
  ///  * [ButtonTextTheme.primary], returns [Colors.transparent].
  Color getHighlightColor(MaterialButton button) {
    if (button.highlightColor != null)
728
      return button.highlightColor!;
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return _highlightColor ?? getTextColor(button).withOpacity(0.16);
      case ButtonTextTheme.primary:
        return Colors.transparent;
    }
  }

  /// The [button]'s elevation when it is enabled and has not been pressed.
  ///
  /// Returns the button's [MaterialButton.elevation] if it is non-null.
  ///
  /// If button is a [FlatButton] then elevation is 0.0, otherwise it is 2.0.
  double getElevation(MaterialButton button) {
    if (button.elevation != null)
746
      return button.elevation!;
747 748 749 750 751
    if (button is FlatButton)
      return 0.0;
    return 2.0;
  }

752 753 754 755 756 757 758 759
  /// The [button]'s elevation when it is enabled and has focus.
  ///
  /// Returns the button's [MaterialButton.focusElevation] if it is non-null.
  ///
  /// If button is a [FlatButton] or an [OutlineButton] then the focus
  /// elevation is 0.0, otherwise the highlight elevation is 4.0.
  double getFocusElevation(MaterialButton button) {
    if (button.focusElevation != null)
760
      return button.focusElevation!;
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
    if (button is FlatButton)
      return 0.0;
    if (button is OutlineButton)
      return 0.0;
    return 4.0;
  }

  /// The [button]'s elevation when it is enabled and has focus.
  ///
  /// Returns the button's [MaterialButton.hoverElevation] if it is non-null.
  ///
  /// If button is a [FlatButton] or an [OutlineButton] then the hover
  /// elevation is 0.0, otherwise the highlight elevation is 4.0.
  double getHoverElevation(MaterialButton button) {
    if (button.hoverElevation != null)
776
      return button.hoverElevation!;
777 778 779 780 781 782 783
    if (button is FlatButton)
      return 0.0;
    if (button is OutlineButton)
      return 0.0;
    return 4.0;
  }

784 785 786 787
  /// The [button]'s elevation when it is enabled and has been pressed.
  ///
  /// Returns the button's [MaterialButton.highlightElevation] if it is non-null.
  ///
788
  /// If button is a [FlatButton] or an [OutlineButton] then the highlight
789
  /// elevation is 0.0, otherwise the highlight elevation is 8.0.
790 791
  double getHighlightElevation(MaterialButton button) {
    if (button.highlightElevation != null)
792
      return button.highlightElevation!;
793 794 795
    if (button is FlatButton)
      return 0.0;
    if (button is OutlineButton)
796
      return 0.0;
797 798 799 800 801 802 803 804
    return 8.0;
  }

  /// The [button]'s elevation when [MaterialButton.onPressed] is null (when
  /// MaterialButton.enabled is false).
  ///
  /// Returns the button's [MaterialButton.elevation] if it is non-null.
  ///
805
  /// Otherwise the disabled elevation is 0.0.
806 807
  double getDisabledElevation(MaterialButton button) {
    if (button.disabledElevation != null)
808
      return button.disabledElevation!;
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
    return 0.0;
  }

  /// Padding for the [button]'s child (typically the button's label).
  ///
  /// Returns the button's [MaterialButton.padding] if it is non-null.
  ///
  /// If this is a button constructed with [RaisedButton.icon] or
  /// [FlatButton.icon] or [OutlineButton.icon] then the padding is:
  /// `EdgeInsetsDirectional.only(start: 12.0, end: 16.0)`.
  ///
  /// Otherwise, returns [padding] if it is non-null.
  ///
  /// Otherwise, returns horizontal padding of 24.0 on the left and right if
  /// [getTextTheme] is [ButtonTextTheme.primary], 16.0 on the left and right
  /// otherwise.
  EdgeInsetsGeometry getPadding(MaterialButton button) {
    if (button.padding != null)
827
      return button.padding!;
828 829 830 831 832

    if (button is MaterialButtonWithIconMixin)
      return const EdgeInsetsDirectional.only(start: 12.0, end: 16.0);

    if (_padding != null)
833
      return _padding!;
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const EdgeInsets.symmetric(horizontal: 16.0);
      case ButtonTextTheme.primary:
        return const EdgeInsets.symmetric(horizontal: 24.0);
    }
  }

  /// The shape of the [button]'s [Material].
  ///
  /// Returns the button's [MaterialButton.shape] if it is non-null, otherwise
  /// [shape] is returned.
  ShapeBorder getShape(MaterialButton button) {
    return button.shape ?? shape;
  }

  /// The duration of the [button]'s highlight animation.
  ///
  /// Returns the button's [MaterialButton.animationDuration] it if is non-null,
  /// otherwise 200ms.
  Duration getAnimationDuration(MaterialButton button) {
    return button.animationDuration ?? kThemeChangeDuration;
  }

  /// The [BoxConstraints] that the define the [button]'s size.
  ///
  /// By default this method just returns [constraints]. Subclasses
  /// could override this method to return a value that was,
  /// for example, based on the button's type.
  BoxConstraints getConstraints(MaterialButton button) => constraints;

  /// The minimum size of the [button]'s tap target.
  ///
869
  /// Returns the button's [MaterialButton.materialTapTargetSize] if it is non-null.
870
  ///
871
  /// Otherwise the value of the `materialTapTargetSize` constructor
872 873 874 875 876 877 878
  /// parameter is returned if that's non-null.
  ///
  /// Otherwise [MaterialTapTargetSize.padded] is returned.
  MaterialTapTargetSize getMaterialTapTargetSize(MaterialButton button) {
    return button.materialTapTargetSize ?? _materialTapTargetSize ?? MaterialTapTargetSize.padded;
  }

879 880 881
  /// Creates a copy of this button theme data object with the matching fields
  /// replaced with the non-null parameter values.
  ButtonThemeData copyWith({
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896
    ButtonTextTheme? textTheme,
    ButtonBarLayoutBehavior? layoutBehavior,
    double? minWidth,
    double? height,
    EdgeInsetsGeometry? padding,
    ShapeBorder? shape,
    bool? alignedDropdown,
    Color? buttonColor,
    Color? disabledColor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    Color? splashColor,
    ColorScheme? colorScheme,
    MaterialTapTargetSize? materialTapTargetSize,
897
  }) {
898
    return ButtonThemeData(
899
      textTheme: textTheme ?? this.textTheme,
900
      layoutBehavior: layoutBehavior ?? this.layoutBehavior,
901 902 903 904 905
      minWidth: minWidth ?? this.minWidth,
      height: height ?? this.height,
      padding: padding ?? this.padding,
      shape: shape ?? this.shape,
      alignedDropdown: alignedDropdown ?? this.alignedDropdown,
906 907
      buttonColor: buttonColor ?? _buttonColor,
      disabledColor: disabledColor ?? _disabledColor,
908 909
      focusColor: focusColor ?? _focusColor,
      hoverColor: hoverColor ?? _hoverColor,
910 911 912 913
      highlightColor: highlightColor ?? _highlightColor,
      splashColor: splashColor ?? _splashColor,
      colorScheme: colorScheme ?? this.colorScheme,
      materialTapTargetSize: materialTapTargetSize ?? _materialTapTargetSize,
914 915 916
    );
  }

917
  @override
918
  bool operator ==(Object other) {
919 920
    if (other.runtimeType != runtimeType)
      return false;
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
    return other is ButtonThemeData
        && other.textTheme == textTheme
        && other.minWidth == minWidth
        && other.height == height
        && other.padding == padding
        && other.shape == shape
        && other.alignedDropdown == alignedDropdown
        && other._buttonColor == _buttonColor
        && other._disabledColor == _disabledColor
        && other._focusColor == _focusColor
        && other._hoverColor == _hoverColor
        && other._highlightColor == _highlightColor
        && other._splashColor == _splashColor
        && other.colorScheme == colorScheme
        && other._materialTapTargetSize == _materialTapTargetSize;
936 937 938 939 940 941 942 943 944 945
  }

  @override
  int get hashCode {
    return hashValues(
      textTheme,
      minWidth,
      height,
      padding,
      shape,
946
      alignedDropdown,
947 948
      _buttonColor,
      _disabledColor,
949 950
      _focusColor,
      _hoverColor,
951 952 953 954
      _highlightColor,
      _splashColor,
      colorScheme,
      _materialTapTargetSize,
955 956
    );
  }
957 958

  @override
959 960
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
961
    const ButtonThemeData defaultTheme = ButtonThemeData();
962 963 964 965 966 967
    properties.add(EnumProperty<ButtonTextTheme>('textTheme', textTheme, defaultValue: defaultTheme.textTheme));
    properties.add(DoubleProperty('minWidth', minWidth, defaultValue: defaultTheme.minWidth));
    properties.add(DoubleProperty('height', height, defaultValue: defaultTheme.height));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: defaultTheme.padding));
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultTheme.shape));
    properties.add(FlagProperty('alignedDropdown',
968 969 970 971
      value: alignedDropdown,
      defaultValue: defaultTheme.alignedDropdown,
      ifTrue: 'dropdown width matches button',
    ));
972 973 974 975 976 977
    properties.add(ColorProperty('buttonColor', _buttonColor, defaultValue: null));
    properties.add(ColorProperty('disabledColor', _disabledColor, defaultValue: null));
    properties.add(ColorProperty('focusColor', _focusColor, defaultValue: null));
    properties.add(ColorProperty('hoverColor', _hoverColor, defaultValue: null));
    properties.add(ColorProperty('highlightColor', _highlightColor, defaultValue: null));
    properties.add(ColorProperty('splashColor', _splashColor, defaultValue: null));
978 979
    properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultTheme.colorScheme));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', _materialTapTargetSize, defaultValue: null));
980
  }
981
}