button_theme.dart 28.8 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
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'material_button.dart';
12
import 'material_state.dart';
13
import 'theme.dart';
14
import 'theme_data.dart' show MaterialTapTargetSize;
15

16 17 18
// Examples can assume:
// late BuildContext context;

19 20 21 22 23 24 25 26 27 28 29 30 31 32
/// 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.
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,
}

33 34 35 36 37 38
/// 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
39
  /// Material Design specification.
40 41 42 43 44 45
  constrained,

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

46 47
/// Used with [ButtonThemeData] to configure the color and geometry of buttons.
///
48 49
/// This class is planned to be deprecated in a future release.
/// Please use one or more of these buttons and associated themes instead:
50
///
51
///  * [ElevatedButton], [ElevatedButtonTheme], [ElevatedButtonThemeData],
52
///  * [FilledButton], [FilledButtonTheme], [FilledButtonThemeData],
53
///  * [OutlinedButton], [OutlinedButtonTheme], [OutlinedButtonThemeData]
54
///  * [TextButton], [TextButtonTheme], [TextButtonThemeData],
55
///
56
/// A button theme can be specified as part of the overall Material theme
Josh Soref's avatar
Josh Soref committed
57
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
58 59 60
/// can be overridden with [ButtonTheme].
///
/// The actual appearance of buttons depends on the button theme, the
61
/// button's enabled state, its elevation (if any), and the overall [Theme].
62 63 64 65 66
///
/// See also:
///
///  * [RawMaterialButton], which can be used to configure a button that doesn't
///    depend on any inherited themes.
67
class ButtonTheme extends InheritedTheme {
68 69
  /// Creates a button theme.
  ///
70 71
  /// The [textTheme], [minWidth], [height], and [colorScheme] arguments
  /// must not be null.
72
  ButtonTheme({
73
    super.key,
74
    ButtonTextTheme textTheme = ButtonTextTheme.normal,
75
    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
76 77
    double minWidth = 88.0,
    double height = 36.0,
78 79
    EdgeInsetsGeometry? padding,
    ShapeBorder? shape,
80
    bool alignedDropdown = false,
81 82 83 84 85 86 87 88
    Color? buttonColor,
    Color? disabledColor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    Color? splashColor,
    ColorScheme? colorScheme,
    MaterialTapTargetSize? materialTapTargetSize,
89
    required super.child,
90 91
  }) : assert(minWidth >= 0.0),
       assert(height >= 0.0),
92
       data = ButtonThemeData(
93 94 95 96 97
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
98 99
         alignedDropdown: alignedDropdown,
         layoutBehavior: layoutBehavior,
100 101
         buttonColor: buttonColor,
         disabledColor: disabledColor,
102 103
         focusColor: focusColor,
         hoverColor: hoverColor,
104 105 106 107
         highlightColor: highlightColor,
         splashColor: splashColor,
         colorScheme: colorScheme,
         materialTapTargetSize: materialTapTargetSize,
108
       );
109

110 111 112 113
  /// Creates a button theme from [data].
  ///
  /// The [data] argument must not be null.
  const ButtonTheme.fromButtonThemeData({
114
    super.key,
115
    required this.data,
116
    required super.child,
117
  });
118 119 120 121 122 123 124 125 126 127 128 129

  /// 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) {
130 131
    final ButtonTheme? inheritedButtonTheme = context.dependOnInheritedWidgetOfExactType<ButtonTheme>();
    ButtonThemeData? buttonTheme = inheritedButtonTheme?.data;
132
    if (buttonTheme?.colorScheme == null) { // if buttonTheme or buttonTheme.colorScheme is null
133
      final ThemeData theme = Theme.of(context);
134
      buttonTheme ??= theme.buttonTheme;
135 136 137 138 139 140
      if (buttonTheme.colorScheme == null) {
        buttonTheme = buttonTheme.copyWith(
          colorScheme: theme.buttonTheme.colorScheme ?? theme.colorScheme,
        );
        assert(buttonTheme.colorScheme != null);
      }
141
    }
142
    return buttonTheme!;
143 144
  }

145 146
  @override
  Widget wrap(BuildContext context, Widget child) {
147
    return ButtonTheme.fromButtonThemeData(data: data, child: child);
148 149
  }

150
  @override
151
  bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
152 153 154 155
}

/// Used with [ButtonTheme] to configure the color and geometry of buttons.
///
156 157
/// This class is planned to be deprecated in a future release.
/// Please use one or more of these buttons and associated themes instead:
158 159 160 161 162
///
///  * [TextButton], [TextButtonTheme], [TextButtonThemeData],
///  * [ElevatedButton], [ElevatedButtonTheme], [ElevatedButtonThemeData],
///  * [OutlinedButton], [OutlinedButtonTheme], [OutlinedButtonThemeData]
///
163
/// A button theme can be specified as part of the overall Material theme
Josh Soref's avatar
Josh Soref committed
164
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
165
/// can be overridden with [ButtonTheme].
166
@immutable
167
class ButtonThemeData with Diagnosticable {
168 169 170
  /// Create a button theme object that can be used with [ButtonTheme]
  /// or [ThemeData].
  ///
171
  /// The [textTheme], [minWidth], [height], [alignedDropdown], and
172 173 174 175
  /// [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
176
  /// have a name with a `get` prefix are used to configure a
177
  /// [RawMaterialButton].
178
  const ButtonThemeData({
179 180 181
    this.textTheme = ButtonTextTheme.normal,
    this.minWidth = 88.0,
    this.height = 36.0,
182 183
    EdgeInsetsGeometry? padding,
    ShapeBorder? shape,
184
    this.layoutBehavior = ButtonBarLayoutBehavior.padded,
185
    this.alignedDropdown = false,
186 187 188 189 190 191
    Color? buttonColor,
    Color? disabledColor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    Color? splashColor,
192
    this.colorScheme,
193
    MaterialTapTargetSize? materialTapTargetSize,
194 195
  }) : assert(minWidth >= 0.0),
       assert(height >= 0.0),
196 197
       _buttonColor = buttonColor,
       _disabledColor = disabledColor,
198 199
       _focusColor = focusColor,
       _hoverColor = hoverColor,
200 201
       _highlightColor = highlightColor,
       _splashColor = splashColor,
202
       _padding = padding,
203 204
       _shape = shape,
       _materialTapTargetSize = materialTapTargetSize;
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

  /// 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.
221 222 223
  ///
  /// Despite the name, this property is not a [TextTheme], its value is not a
  /// collection of [TextStyle]s.
224 225
  final ButtonTextTheme textTheme;

226 227
  /// Defines whether a [ButtonBar] should size itself with a minimum size
  /// constraint or with padding.
228 229 230 231
  ///
  /// Defaults to [ButtonBarLayoutBehavior.padded].
  final ButtonBarLayoutBehavior layoutBehavior;

232
  /// Convenience that returns [minWidth] and [height] as a
233
  /// [BoxConstraints] object.
234
  BoxConstraints get constraints {
235
    return BoxConstraints(
236 237 238 239 240 241 242 243 244
      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.
245 246 247
  ///
  /// See also:
  ///
248 249
  ///  * [getPadding], which is used to calculate padding for the [button]'s
  ///    child (typically the button's label).
250
  EdgeInsetsGeometry get padding {
251
    if (_padding != null) {
252
      return _padding!;
253
    }
254
    switch (textTheme) {
255 256 257 258
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const EdgeInsets.symmetric(horizontal: 16.0);
      case ButtonTextTheme.primary:
259
        return const EdgeInsets.symmetric(horizontal: 24.0);
260 261
    }
  }
262
  final EdgeInsetsGeometry? _padding;
263 264 265 266 267 268 269 270 271 272

  /// 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.
273 274 275
  ///
  /// See also:
  ///
276 277
  ///  * [getShape], which is used to calculate the shape of the [button]'s
  ///    [Material].
278
  ShapeBorder get shape {
279
    if (_shape != null) {
280
      return _shape!;
281
    }
282 283 284 285
    switch (textTheme) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const RoundedRectangleBorder(
286
          borderRadius: BorderRadius.all(Radius.circular(2.0)),
287 288 289
        );
      case ButtonTextTheme.primary:
        return const RoundedRectangleBorder(
290
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
291 292 293
        );
    }
  }
294
  final ShapeBorder? _shape;
295

296 297 298 299 300 301 302 303 304 305 306
  /// 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;

307
  /// The background fill color.
308 309 310
  ///
  /// This property is null by default.
  ///
311 312
  /// If the button is in the focused, hovering, or highlighted state, then the
  /// [focusColor], [hoverColor], or [highlightColor] will take precedence over
313
  /// the [buttonColor].
314
  ///
315 316
  /// See also:
  ///
317
  ///  * [getFillColor], which is used to compute the background fill color.
318
  final Color? _buttonColor;
319

320
  /// The background fill color when disabled.
321 322 323 324 325
  ///
  /// This property is null by default.
  ///
  /// See also:
  ///
326 327
  ///  * [getDisabledFillColor], which is to compute background fill color for
  ///    disabled state.
328
  final Color? _disabledColor;
329

330 331 332 333 334 335 336 337 338
  /// 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:
  ///
339 340
  ///  * [getFocusColor], which is used to compute the fill color of the button
  ///    when it has input focus.
341
  final Color? _focusColor;
342 343 344 345 346 347 348 349 350 351

  /// 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:
  ///
352 353
  ///  * [getHoverColor], which is used to compute the fill color of the button
  ///    when it has input focus.
354
  final Color? _hoverColor;
355

356 357 358 359 360 361
  /// The color of the overlay that appears when a button is pressed.
  ///
  /// This property is null by default.
  ///
  /// See also:
  ///
362 363
  ///  * [getHighlightColor], which is used to compute the color of the overlay
  ///    that appears when the [button] is pressed.
364
  final Color? _highlightColor;
365 366 367 368 369 370 371

  /// The color of the ink "splash" overlay that appears when a button is tapped.
  ///
  /// This property is null by default.
  ///
  /// See also:
  ///
372 373
  ///  * [getSplashColor], which is used to compute the color of the ink
  ///    "splash" overlay that appears when the (enabled) [button] is tapped.
374
  final Color? _splashColor;
375 376 377 378

  /// A set of thirteen colors that can be used to derive the button theme's
  /// colors.
  ///
379 380
  /// This property was added much later than the theme's set of highly specific
  /// colors, like [ThemeData.highlightColor] and [ThemeData.splashColor] etc.
381
  ///
382 383 384
  /// 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.
385
  final ColorScheme? colorScheme;
386 387 388 389

  // The minimum size of a button's tap target.
  //
  // This property is null by default.
390
  final MaterialTapTargetSize? _materialTapTargetSize;
391 392 393 394 395 396

  /// 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) {
397
    return button.colorBrightness ?? colorScheme!.brightness;
398 399 400 401 402 403 404
  }

  /// 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].
405
  ButtonTextTheme getTextTheme(MaterialButton button) => button.textTheme ?? textTheme;
406 407 408 409 410 411

  /// 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
412
  /// with its opacity set to 0.38.
413
  ///
414 415
  /// If [MaterialButton.textColor] is a [MaterialStateProperty<Color>], it will be
  /// used as the `disabledTextColor`. It will be resolved in the [MaterialState.disabled] state.
416
  Color getDisabledTextColor(MaterialButton button) {
417
    return button.textColor ?? button.disabledTextColor ?? colorScheme!.onSurface.withOpacity(0.38);
418 419 420
  }

  /// The [button]'s background color when [MaterialButton.onPressed] is null
421
  /// (when [MaterialButton.enabled] is false).
422 423 424
  ///
  /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
  ///
425
  /// Otherwise the value of the `disabledColor` constructor parameter
426 427 428
  /// is returned, if it is non-null.
  ///
  /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
429
  /// with its opacity set to 0.38.
430
  Color getDisabledFillColor(MaterialButton button) {
431
    return button.disabledColor ?? _disabledColor ?? colorScheme!.onSurface.withOpacity(0.38);
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
  }

  /// 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 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.
453 454
  Color? getFillColor(MaterialButton button) {
    final Color? fillColor = button.enabled ? button.color : button.disabledColor;
455
    if (fillColor != null) {
456
      return fillColor;
457
    }
458

459
    if (button.runtimeType == MaterialButton) {
460
      return null;
461
    }
462

463
    if (button.enabled && _buttonColor != null) {
464
      return _buttonColor;
465
    }
466 467 468 469

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
470
        return button.enabled ? colorScheme!.primary : getDisabledFillColor(button);
471 472
      case ButtonTextTheme.primary:
        return button.enabled
473 474
          ? _buttonColor ?? colorScheme!.primary
          : colorScheme!.onSurface.withOpacity(0.12);
475 476 477 478 479 480 481
    }
  }

  /// 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
482 483
  /// [MaterialButton.textColor] is non-null, then [MaterialButton.textColor]
  /// is returned.
484 485 486 487
  ///
  /// Otherwise the text color depends on the value of [getTextTheme]
  /// and [getBrightness].
  ///
488 489 490
  ///  * [ButtonTextTheme.normal]: [Colors.white] is used if [getBrightness]
  ///    resolves to [Brightness.dark]. [Colors.black87] is used if
  ///    [getBrightness] resolves to [Brightness.light].
491
  ///  * [ButtonTextTheme.accent]: [ColorScheme.secondary] of [colorScheme].
492
  ///  * [ButtonTextTheme.primary]: If [getFillColor] is dark then [Colors.white],
493
  ///    otherwise [Colors.black].
494
  Color getTextColor(MaterialButton button) {
495
    if (!button.enabled) {
496
      return getDisabledTextColor(button);
497
    }
498

499
    if (button.textColor != null) {
500
      return button.textColor!;
501
    }
502 503 504 505 506 507

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

      case ButtonTextTheme.accent:
508
        return colorScheme!.secondary;
509

510
      case ButtonTextTheme.primary:
511
        final Color? fillColor = getFillColor(button);
512 513 514
        final bool fillIsDark = fillColor != null
          ? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
          : getBrightness(button) == Brightness.dark;
515
        return fillIsDark ? Colors.white : Colors.black;
516 517 518 519 520 521 522 523 524
    }
  }

  /// 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
525
  /// it is non-null.
526 527
  ///
  /// Otherwise, returns the value of the `splashColor` constructor parameter
528
  /// if it is non-null and [getTextTheme] is not [ButtonTextTheme.primary].
529 530 531
  ///
  /// Otherwise, returns [getTextColor] with an opacity of 0.12.
  Color getSplashColor(MaterialButton button) {
532
    if (button.splashColor != null) {
533
      return button.splashColor!;
534
    }
535

536
    if (_splashColor != null) {
537 538 539
      switch (getTextTheme(button)) {
        case ButtonTextTheme.normal:
        case ButtonTextTheme.accent:
540
          return _splashColor!;
541 542 543 544 545 546 547 548
        case ButtonTextTheme.primary:
          break;
      }
    }

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

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  /// 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);
  }

575 576 577 578 579 580 581 582 583 584
  /// 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) {
585
    if (button.highlightColor != null) {
586
      return button.highlightColor!;
587
    }
588 589 590 591 592 593 594 595 596 597 598 599

    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.
  ///
600 601 602
  /// Returns the button's [MaterialButton.elevation] if it is non-null,
  /// otherwise it is 2.0.
  double getElevation(MaterialButton button) => button.elevation ?? 2.0;
603

604 605
  /// The [button]'s elevation when it is enabled and has focus.
  ///
606 607 608
  /// Returns the button's [MaterialButton.focusElevation] if it is non-null,
  /// otherwise the highlight elevation is 4.0.
  double getFocusElevation(MaterialButton button) => button.focusElevation ?? 4.0;
609 610 611

  /// The [button]'s elevation when it is enabled and has focus.
  ///
612 613 614
  /// Returns the button's [MaterialButton.hoverElevation] if it is non-null,
  /// otherwise the highlight elevation is 4.0.
  double getHoverElevation(MaterialButton button) => button.hoverElevation ?? 4.0;
615

616 617
  /// The [button]'s elevation when it is enabled and has been pressed.
  ///
618 619 620
  /// Returns the button's [MaterialButton.highlightElevation] if it is non-null,
  /// otherwise the highlight elevation is 8.0.
  double getHighlightElevation(MaterialButton button) => button.highlightElevation ?? 8.0;
621 622 623 624 625 626

  /// 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.
  ///
627
  /// Otherwise the disabled elevation is 0.0.
628
  double getDisabledElevation(MaterialButton button) => button.disabledElevation ?? 0.0;
629 630 631

  /// Padding for the [button]'s child (typically the button's label).
  ///
632 633 634
  /// Returns the button's [MaterialButton.padding] if it is non-null,
  /// otherwise, returns the `padding` of the constructor parameter if it is
  /// non-null.
635 636 637 638 639
  ///
  /// 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) {
640
    if (button.padding != null) {
641
      return button.padding!;
642
    }
643

644
    if (button is MaterialButtonWithIconMixin) {
645
      return const EdgeInsetsDirectional.only(start: 12.0, end: 16.0);
646
    }
647

648
    if (_padding != null) {
649
      return _padding!;
650
    }
651 652 653 654 655 656 657 658 659 660 661 662 663 664

    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.
665
  ShapeBorder getShape(MaterialButton button) => button.shape ?? shape;
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683

  /// 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.
  ///
684
  /// Returns the button's [MaterialButton.materialTapTargetSize] if it is non-null.
685
  ///
686
  /// Otherwise the value of the `materialTapTargetSize` constructor
687 688 689 690 691 692 693
  /// parameter is returned if that's non-null.
  ///
  /// Otherwise [MaterialTapTargetSize.padded] is returned.
  MaterialTapTargetSize getMaterialTapTargetSize(MaterialButton button) {
    return button.materialTapTargetSize ?? _materialTapTargetSize ?? MaterialTapTargetSize.padded;
  }

694 695 696
  /// Creates a copy of this button theme data object with the matching fields
  /// replaced with the non-null parameter values.
  ButtonThemeData copyWith({
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
    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,
712
  }) {
713
    return ButtonThemeData(
714
      textTheme: textTheme ?? this.textTheme,
715
      layoutBehavior: layoutBehavior ?? this.layoutBehavior,
716 717 718 719 720
      minWidth: minWidth ?? this.minWidth,
      height: height ?? this.height,
      padding: padding ?? this.padding,
      shape: shape ?? this.shape,
      alignedDropdown: alignedDropdown ?? this.alignedDropdown,
721 722
      buttonColor: buttonColor ?? _buttonColor,
      disabledColor: disabledColor ?? _disabledColor,
723 724
      focusColor: focusColor ?? _focusColor,
      hoverColor: hoverColor ?? _hoverColor,
725 726 727 728
      highlightColor: highlightColor ?? _highlightColor,
      splashColor: splashColor ?? _splashColor,
      colorScheme: colorScheme ?? this.colorScheme,
      materialTapTargetSize: materialTapTargetSize ?? _materialTapTargetSize,
729 730 731
    );
  }

732
  @override
733
  bool operator ==(Object other) {
734
    if (other.runtimeType != runtimeType) {
735
      return false;
736
    }
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
    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;
752 753 754
  }

  @override
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
  int get hashCode => Object.hash(
    textTheme,
    minWidth,
    height,
    padding,
    shape,
    alignedDropdown,
    _buttonColor,
    _disabledColor,
    _focusColor,
    _hoverColor,
    _highlightColor,
    _splashColor,
    colorScheme,
    _materialTapTargetSize,
  );
771 772

  @override
773 774
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
775
    const ButtonThemeData defaultTheme = ButtonThemeData();
776 777 778 779 780 781
    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',
782 783 784 785
      value: alignedDropdown,
      defaultValue: defaultTheme.alignedDropdown,
      ifTrue: 'dropdown width matches button',
    ));
786 787 788 789 790 791
    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));
792 793
    properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultTheme.colorScheme));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', _materialTapTargetSize, defaultValue: null));
794
  }
795
}