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

5 6
// @dart = 2.8

7 8 9
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

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

/// 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:
///
27 28
///  * [RaisedButton], [FlatButton], [OutlineButton], which are configured
///    based on the ambient [ButtonTheme].
29 30 31 32 33 34 35 36 37 38 39
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,
}

40 41 42 43 44 45 46 47 48 49 50 51 52
/// 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,
}

53 54 55
/// Used with [ButtonThemeData] to configure the color and geometry of buttons.
///
/// A button theme can be specified as part of the overall Material theme
Josh Soref's avatar
Josh Soref committed
56
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
57 58 59
/// can be overridden with [ButtonTheme].
///
/// The actual appearance of buttons depends on the button theme, the
60
/// button's enabled state, its elevation (if any), and the overall [Theme].
61 62 63
///
/// See also:
///
64 65
///  * [FlatButton] [RaisedButton], and [OutlineButton], which are styled
///    based on the ambient button theme.
66 67
///  * [RawMaterialButton], which can be used to configure a button that doesn't
///    depend on any inherited themes.
68
class ButtonTheme extends InheritedTheme {
69 70
  /// Creates a button theme.
  ///
71 72
  /// The [textTheme], [minWidth], [height], and [colorScheme] arguments
  /// must not be null.
73 74
  ButtonTheme({
    Key key,
75
    ButtonTextTheme textTheme = ButtonTextTheme.normal,
76
    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
77 78
    double minWidth = 88.0,
    double height = 36.0,
79 80
    EdgeInsetsGeometry padding,
    ShapeBorder shape,
81
    bool alignedDropdown = false,
82 83
    Color buttonColor,
    Color disabledColor,
84 85
    Color focusColor,
    Color hoverColor,
86 87
    Color highlightColor,
    Color splashColor,
88
    ColorScheme colorScheme,
89
    MaterialTapTargetSize materialTapTargetSize,
90 91 92 93
    Widget child,
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
94
       assert(alignedDropdown != null),
95
       assert(layoutBehavior != null),
96
       data = ButtonThemeData(
97 98 99 100 101
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
102 103
         alignedDropdown: alignedDropdown,
         layoutBehavior: layoutBehavior,
104 105
         buttonColor: buttonColor,
         disabledColor: disabledColor,
106 107
         focusColor: focusColor,
         hoverColor: hoverColor,
108 109 110 111
         highlightColor: highlightColor,
         splashColor: splashColor,
         colorScheme: colorScheme,
         materialTapTargetSize: materialTapTargetSize,
112 113 114
       ),
       super(key: key, child: child);

115 116 117 118 119 120 121 122 123 124
  /// Creates a button theme from [data].
  ///
  /// The [data] argument must not be null.
  const ButtonTheme.fromButtonThemeData({
    Key key,
    @required this.data,
    Widget child,
  }) : assert(data != null),
       super(key: key, child: child);

125 126 127
  /// Creates a button theme that is appropriate for button bars, as used in
  /// dialog footers and in the headers of data tables.
  ///
128 129
  /// Deprecated. Please use [ButtonBarTheme] instead which offers more
  /// flexibility to configure [ButtonBar] widgets.
130
  ///
131
  /// To migrate instances of code that were just wrapping a [ButtonBar]:
132
  ///
133 134 135 136 137 138 139 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
  /// ```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.
165 166 167 168
  @Deprecated(
    'Use ButtonBarTheme instead. '
    'This feature was deprecated after v1.9.1.'
  )
169 170
  ButtonTheme.bar({
    Key key,
171 172 173 174
    ButtonTextTheme textTheme = ButtonTextTheme.accent,
    double minWidth = 64.0,
    double height = 36.0,
    EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0),
175
    ShapeBorder shape,
176
    bool alignedDropdown = false,
177 178
    Color buttonColor,
    Color disabledColor,
179 180
    Color focusColor,
    Color hoverColor,
181 182
    Color highlightColor,
    Color splashColor,
183
    ColorScheme colorScheme,
184
    Widget child,
185
    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
186 187 188
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
189
       assert(alignedDropdown != null),
190
       data = ButtonThemeData(
191 192 193 194 195
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
196
         alignedDropdown: alignedDropdown,
197
         layoutBehavior: layoutBehavior,
198 199
         buttonColor: buttonColor,
         disabledColor: disabledColor,
200 201
         focusColor: focusColor,
         hoverColor: hoverColor,
202 203 204
         highlightColor: highlightColor,
         splashColor: splashColor,
         colorScheme: colorScheme,
205 206 207 208 209 210 211 212 213 214 215 216 217 218
       ),
       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) {
219
    final ButtonTheme inheritedButtonTheme = context.dependOnInheritedWidgetOfExactType<ButtonTheme>();
220
    ButtonThemeData buttonTheme = inheritedButtonTheme?.data;
221
    if (buttonTheme?.colorScheme == null) { // if buttonTheme or buttonTheme.colorScheme is null
222 223
      final ThemeData theme = Theme.of(context);
      buttonTheme ??= theme.buttonTheme;
224 225 226 227 228 229
      if (buttonTheme.colorScheme == null) {
        buttonTheme = buttonTheme.copyWith(
          colorScheme: theme.buttonTheme.colorScheme ?? theme.colorScheme,
        );
        assert(buttonTheme.colorScheme != null);
      }
230 231
    }
    return buttonTheme;
232 233
  }

234 235
  @override
  Widget wrap(BuildContext context, Widget child) {
236
    final ButtonTheme ancestorTheme = context.findAncestorWidgetOfExactType<ButtonTheme>();
237 238 239
    return identical(this, ancestorTheme) ? child : ButtonTheme.fromButtonThemeData(data: data, child: child);
  }

240
  @override
241
  bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
242 243 244 245 246
}

/// Used with [ButtonTheme] to configure the color and geometry of buttons.
///
/// A button theme can be specified as part of the overall Material theme
Josh Soref's avatar
Josh Soref committed
247
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
248
/// can be overridden with [ButtonTheme].
249
@immutable
250
class ButtonThemeData with Diagnosticable {
251 252 253
  /// Create a button theme object that can be used with [ButtonTheme]
  /// or [ThemeData].
  ///
254 255 256 257 258 259 260
  /// The [textTheme], [minWidth], [height], [alignedDropDown], and
  /// [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].
261
  const ButtonThemeData({
262 263 264
    this.textTheme = ButtonTextTheme.normal,
    this.minWidth = 88.0,
    this.height = 36.0,
265 266
    EdgeInsetsGeometry padding,
    ShapeBorder shape,
267
    this.layoutBehavior = ButtonBarLayoutBehavior.padded,
268
    this.alignedDropdown = false,
269 270
    Color buttonColor,
    Color disabledColor,
271 272
    Color focusColor,
    Color hoverColor,
273 274
    Color highlightColor,
    Color splashColor,
275
    this.colorScheme,
276
    MaterialTapTargetSize materialTapTargetSize,
277 278 279
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
280
       assert(alignedDropdown != null),
281
       assert(layoutBehavior != null),
282 283
       _buttonColor = buttonColor,
       _disabledColor = disabledColor,
284 285
       _focusColor = focusColor,
       _hoverColor = hoverColor,
286 287
       _highlightColor = highlightColor,
       _splashColor = splashColor,
288
       _padding = padding,
289 290
       _shape = shape,
       _materialTapTargetSize = materialTapTargetSize;
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306

  /// 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.
307 308 309
  ///
  /// Despite the name, this property is not a [TextTheme], its value is not a
  /// collection of [TextStyle]s.
310 311
  final ButtonTextTheme textTheme;

312 313
  /// Defines whether a [ButtonBar] should size itself with a minimum size
  /// constraint or with padding.
314 315 316 317
  ///
  /// Defaults to [ButtonBarLayoutBehavior.padded].
  final ButtonBarLayoutBehavior layoutBehavior;

318 319
  /// Simply a convenience that returns [minWidth] and [height] as a
  /// [BoxConstraints] object:
320
  ///
321
  /// ```dart
322
  /// return BoxConstraints(
323
  ///   minWidth: minWidth,
324
  ///   minHeight: height,
325 326 327
  /// );
  /// ```
  BoxConstraints get constraints {
328
    return BoxConstraints(
329 330 331 332 333 334 335 336 337
      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.
338 339 340 341 342
  ///
  /// See also:
  ///
  ///  * [getPadding], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
343 344 345
  EdgeInsetsGeometry get padding {
    if (_padding != null)
      return _padding;
346
    switch (textTheme) {
347 348 349 350
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const EdgeInsets.symmetric(horizontal: 16.0);
      case ButtonTextTheme.primary:
351
        return const EdgeInsets.symmetric(horizontal: 24.0);
352
    }
353
    assert(false);
354 355 356 357 358 359 360 361 362 363 364 365 366
    return EdgeInsets.zero;
  }
  final EdgeInsetsGeometry _padding;

  /// 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.
367 368 369 370 371
  ///
  /// See also:
  ///
  ///  * [getShape], which is used by [RaisedButton], [OutlineButton]
  ///    and [FlatButton].
372 373 374 375 376 377 378
  ShapeBorder get shape {
    if (_shape != null)
      return _shape;
    switch (textTheme) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const RoundedRectangleBorder(
379
          borderRadius: BorderRadius.all(Radius.circular(2.0)),
380 381 382
        );
      case ButtonTextTheme.primary:
        return const RoundedRectangleBorder(
383
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
384 385 386 387 388 389
        );
    }
    return const RoundedRectangleBorder();
  }
  final ShapeBorder _shape;

390 391 392 393 394 395 396 397 398 399 400
  /// 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;

401 402 403 404
  /// The background fill color for [RaisedButton]s.
  ///
  /// This property is null by default.
  ///
405 406
  /// If the button is in the focused, hovering, or highlighted state, then the
  /// [focusColor], [hoverColor], or [highlightColor] will take precedence over
407
  /// the [buttonColor].
408
  ///
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
  /// See also:
  ///
  ///  * [getFillColor], which is used by [RaisedButton] to compute its
  ///    background fill color.
  final Color _buttonColor;

  /// 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.
  final Color _disabledColor;

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
  /// 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].
  final Color _focusColor;

  /// 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].
  final Color _hoverColor;

451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
  /// 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].
  final Color _highlightColor;

  /// 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].
  final Color _splashColor;

  /// 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.
  final ColorScheme colorScheme;

  // 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].
  final MaterialTapTargetSize _materialTapTargetSize;

  /// 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) {
    return button.colorBrightness ?? colorScheme.brightness;
  }

  /// 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
515
  /// with its opacity set to 0.38.
516
  ///
517 518
  /// If [MaterialButton.textColor] is a [MaterialStateProperty<Color>], it will be
  /// used as the `disabledTextColor`. It will be resolved in the [MaterialState.disabled] state.
519
  Color getDisabledTextColor(MaterialButton button) {
520
    if (button.textColor is MaterialStateProperty<Color>)
521
      return button.textColor;
522 523
    if (button.disabledTextColor != null)
      return button.disabledTextColor;
524
    return colorScheme.onSurface.withOpacity(0.38);
525 526 527
  }

  /// The [button]'s background color when [MaterialButton.onPressed] is null
528
  /// (when [MaterialButton.enabled] is false).
529 530 531
  ///
  /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
  ///
532
  /// Otherwise the value of the `disabledColor` constructor parameter
533 534 535
  /// is returned, if it is non-null.
  ///
  /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
536
  /// with its opacity set to 0.38.
537 538 539 540 541
  Color getDisabledFillColor(MaterialButton button) {
    if (button.disabledColor != null)
      return button.disabledColor;
    if (_disabledColor != null)
      return _disabledColor;
542
    return colorScheme.onSurface.withOpacity(0.38);
543 544 545 546 547 548 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 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.
  Color getFillColor(MaterialButton button) {
    final Color fillColor = button.enabled ? button.color : button.disabledColor;
    if (fillColor != null)
      return fillColor;

575
    if (button is FlatButton || button is OutlineButton || button.runtimeType == MaterialButton)
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
      return null;

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

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return button.enabled ? colorScheme.primary : getDisabledFillColor(button);
      case ButtonTextTheme.primary:
        return button.enabled
          ? _buttonColor ?? colorScheme.primary
          : colorScheme.onSurface.withOpacity(0.12);
    }

    assert(false);
    return null;
  }

  /// 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
  /// [buttonTextColor] is non-null, then [buttonTextColor] is returned.
  ///
  /// Otherwise the text color depends on the value of [getTextTheme]
  /// and [getBrightness].
  ///
604 605 606 607 608
  ///  * [ButtonTextTheme.normal]: [Colors.white] is used if [getBrightness]
  ///    resolves to [Brightness.dark]. [Colors.black87] is used if
  ///    [getBrightness] resolves to [Brightness.light].
  ///  * [ButtonTextTheme.accent]: [colorScheme.secondary].
  ///  * [ButtonTextTheme.primary]: If [getFillColor] is dark then [Colors.white],
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
  ///    otherwise if [button] is a [FlatButton] or an [OutlineButton] then
  ///    [colorScheme.primary], otherwise [Colors.black].
  Color getTextColor(MaterialButton button) {
    if (!button.enabled)
      return getDisabledTextColor(button);

    if (button.textColor != null)
      return button.textColor;

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

      case ButtonTextTheme.accent:
        return colorScheme.secondary;

625
      case ButtonTextTheme.primary:
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
        final Color fillColor = getFillColor(button);
        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)
          return colorScheme.primary;
        return Colors.black;
    }

    assert(false);
    return null;
  }

  /// 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)
      return button.splashColor;

    if (_splashColor != null && (button is RaisedButton || button is OutlineButton))
      return _splashColor;

    if (_splashColor != null && button is FlatButton) {
      switch (getTextTheme(button)) {
        case ButtonTextTheme.normal:
        case ButtonTextTheme.accent:
          return _splashColor;
        case ButtonTextTheme.primary:
          break;
      }
    }

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

674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
  /// 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);
  }

700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
  /// 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)
      return button.highlightColor;

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

    assert(false);
    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)
      return button.elevation;
    if (button is FlatButton)
      return 0.0;
    return 2.0;
  }

738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
  /// 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)
      return button.focusElevation;
    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)
      return button.hoverElevation;
    if (button is FlatButton)
      return 0.0;
    if (button is OutlineButton)
      return 0.0;
    return 4.0;
  }

770 771 772 773
  /// The [button]'s elevation when it is enabled and has been pressed.
  ///
  /// Returns the button's [MaterialButton.highlightElevation] if it is non-null.
  ///
774
  /// If button is a [FlatButton] or an [OutlineButton] then the highlight
775
  /// elevation is 0.0, otherwise the highlight elevation is 8.0.
776 777 778 779 780 781
  double getHighlightElevation(MaterialButton button) {
    if (button.highlightElevation != null)
      return button.highlightElevation;
    if (button is FlatButton)
      return 0.0;
    if (button is OutlineButton)
782
      return 0.0;
783 784 785 786 787 788 789 790
    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.
  ///
791
  /// Otherwise the disabled elevation is 0.0.
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 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
  double getDisabledElevation(MaterialButton button) {
    if (button.disabledElevation != null)
      return button.disabledElevation;
    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)
      return button.padding;

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

    if (_padding != null)
      return _padding;

    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);
    }
    assert(false);
    return EdgeInsets.zero;
  }

  /// 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.
  ///
  /// Returns the button's [MaterialButton.tapTargetSize] if it is non-null.
  ///
  /// Otherwise the value of the [materialTapTargetSize] constructor
  /// parameter is returned if that's non-null.
  ///
  /// Otherwise [MaterialTapTargetSize.padded] is returned.
  MaterialTapTargetSize getMaterialTapTargetSize(MaterialButton button) {
    return button.materialTapTargetSize ?? _materialTapTargetSize ?? MaterialTapTargetSize.padded;
  }

867 868 869 870
  /// Creates a copy of this button theme data object with the matching fields
  /// replaced with the non-null parameter values.
  ButtonThemeData copyWith({
    ButtonTextTheme textTheme,
871
    ButtonBarLayoutBehavior layoutBehavior,
872 873 874 875 876
    double minWidth,
    double height,
    EdgeInsetsGeometry padding,
    ShapeBorder shape,
    bool alignedDropdown,
877 878
    Color buttonColor,
    Color disabledColor,
879 880
    Color focusColor,
    Color hoverColor,
881 882 883 884
    Color highlightColor,
    Color splashColor,
    ColorScheme colorScheme,
    MaterialTapTargetSize materialTapTargetSize,
885
  }) {
886
    return ButtonThemeData(
887
      textTheme: textTheme ?? this.textTheme,
888
      layoutBehavior: layoutBehavior ?? this.layoutBehavior,
889 890 891 892 893
      minWidth: minWidth ?? this.minWidth,
      height: height ?? this.height,
      padding: padding ?? this.padding,
      shape: shape ?? this.shape,
      alignedDropdown: alignedDropdown ?? this.alignedDropdown,
894 895
      buttonColor: buttonColor ?? _buttonColor,
      disabledColor: disabledColor ?? _disabledColor,
896 897
      focusColor: focusColor ?? _focusColor,
      hoverColor: hoverColor ?? _hoverColor,
898 899 900 901
      highlightColor: highlightColor ?? _highlightColor,
      splashColor: splashColor ?? _splashColor,
      colorScheme: colorScheme ?? this.colorScheme,
      materialTapTargetSize: materialTapTargetSize ?? _materialTapTargetSize,
902 903 904
    );
  }

905
  @override
906
  bool operator ==(Object other) {
907 908
    if (other.runtimeType != runtimeType)
      return false;
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923
    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;
924 925 926 927 928 929 930 931 932 933
  }

  @override
  int get hashCode {
    return hashValues(
      textTheme,
      minWidth,
      height,
      padding,
      shape,
934
      alignedDropdown,
935 936
      _buttonColor,
      _disabledColor,
937 938
      _focusColor,
      _hoverColor,
939 940 941 942
      _highlightColor,
      _splashColor,
      colorScheme,
      _materialTapTargetSize,
943 944
    );
  }
945 946

  @override
947 948
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
949
    const ButtonThemeData defaultTheme = ButtonThemeData();
950 951 952 953 954 955
    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',
956 957 958 959
      value: alignedDropdown,
      defaultValue: defaultTheme.alignedDropdown,
      ifTrue: 'dropdown width matches button',
    ));
960 961 962 963 964 965
    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));
966 967
    properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultTheme.colorScheme));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', _materialTapTargetSize, defaultValue: null));
968
  }
969
}