button_theme.dart 11.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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';

import 'theme.dart';

/// 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:
///
///  * [RaisedButton], which styles itself based on the ambient [ButtonTheme].
///  * [FlatButton], which styles itself based on the ambient [ButtonTheme].
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,
}

/// 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
32
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
/// can be overridden with [ButtonTheme].
///
/// The actual appearance of buttons depends on the button theme, the
/// button's enabled state, its elevation (if any) and the overall Material
/// theme.
///
/// See also:
///
///  * [FlatButton] and [RaisedButton], which are styled based on the
///    ambient button theme.
///  * [ThemeData.textTheme], `button` is the default text style for button labels.
///  * [ThemeData.buttonColor], the fill color for [RaisedButton]s unless the
///    button theme's text theme is [ButtonTextTheme.primary].
///  * [ThemeData.primaryColor], the fill or text color if a button theme's text
///    theme is [ButtonTextTheme.primary].
///  * [ThemeData.accentColor], the text color for buttons when button theme's
///    text theme is [ButtonTextTheme.accent].
///  * [ThemeData.disabled], the default text color for disabled buttons.
///  * [ThemeData.brightness], used to select contrasting text and fill colors.
///  * [ThemeData.highlightColor], a button [InkWell]'s default highlight color.
///  * [ThemeData.splashColor], a button [InkWell]'s default splash color.
///  * [RawMaterialButton], which can be used to configure a button that doesn't
///    depend on any inherited themes.
class ButtonTheme extends InheritedWidget {
  /// Creates a button theme.
  ///
  /// The [textTheme], [minWidth], and [height] arguments must not be null.
  ButtonTheme({
    Key key,
62 63 64
    ButtonTextTheme textTheme = ButtonTextTheme.normal,
    double minWidth = 88.0,
    double height = 36.0,
65 66
    EdgeInsetsGeometry padding,
    ShapeBorder shape,
67
    bool alignedDropdown = false,
68 69 70 71
    Widget child,
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
72
       assert(alignedDropdown != null),
73 74 75 76 77 78
       data = new ButtonThemeData(
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
79
         alignedDropdown: alignedDropdown
80 81 82
       ),
       super(key: key, child: child);

83 84 85 86 87 88 89 90 91 92
  /// 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);

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
  /// Creates a button theme that is appropriate for button bars, as used in
  /// dialog footers and in the headers of data tables.
  ///
  /// This theme is denser, with a smaller [minWidth] and [padding], than the
  /// default theme. Also, this theme uses [ButtonTextTheme.accent] rather than
  /// [ButtonTextTheme.normal].
  ///
  /// For best effect, the label of the button at the edge of the container
  /// should have text that ends up wider than 64.0 pixels. This ensures that
  /// the alignment of the text matches the alignment of the edge of the
  /// container.
  ///
  /// For example, buttons at the bottom of [Dialog] or [Card] widgets use this
  /// button theme.
  ButtonTheme.bar({
    Key key,
109 110 111 112
    ButtonTextTheme textTheme = ButtonTextTheme.accent,
    double minWidth = 64.0,
    double height = 36.0,
    EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0),
113
    ShapeBorder shape,
114
    bool alignedDropdown = false,
115 116 117 118
    Widget child,
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
119
       assert(alignedDropdown != null),
120 121 122 123 124 125
       data = new ButtonThemeData(
         textTheme: textTheme,
         minWidth: minWidth,
         height: height,
         padding: padding,
         shape: shape,
126
         alignedDropdown: alignedDropdown,
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
       ),
       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) {
    final ButtonTheme result = context.inheritFromWidgetOfExactType(ButtonTheme);
    return result?.data ?? Theme.of(context).buttonTheme;
  }

  @override
146
  bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
147 148 149 150 151
}

/// 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
152
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
153
/// can be overridden with [ButtonTheme].
154
class ButtonThemeData extends Diagnosticable {
155 156 157 158 159
  /// Create a button theme object that can be used with [ButtonTheme]
  /// or [ThemeData].
  ///
  /// The [textTheme], [minWidth], and [height] parameters must not be null.
  const ButtonThemeData({
160 161 162
    this.textTheme = ButtonTextTheme.normal,
    this.minWidth = 88.0,
    this.height = 36.0,
163 164
    EdgeInsetsGeometry padding,
    ShapeBorder shape,
165
    this.alignedDropdown = false,
166 167 168
  }) : assert(textTheme != null),
       assert(minWidth != null && minWidth >= 0.0),
       assert(height != null && height >= 0.0),
169
       assert(alignedDropdown != null),
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
       _padding = padding,
       _shape = shape;

  /// 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.
  final ButtonTextTheme textTheme;

  /// Simply a convenience that returns [minWidth] and [height] as a
  /// [BoxConstraints] object:
  /// ```dart
  /// return new BoxConstraints(
  ///   minWidth: minWidth,
  ///    minHeight: height,
  /// );
  /// ```
  BoxConstraints get constraints {
    return new BoxConstraints(
      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.
  EdgeInsetsGeometry get padding {
    if (_padding != null)
      return _padding;
    switch (textTheme) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const EdgeInsets.symmetric(horizontal: 16.0);
      case ButtonTextTheme.primary:
        return const EdgeInsets.symmetric(horizontal: 24.0);
    }
    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.
  ShapeBorder get shape {
    if (_shape != null)
      return _shape;
    switch (textTheme) {
      case ButtonTextTheme.normal:
      case ButtonTextTheme.accent:
        return const RoundedRectangleBorder(
239
          borderRadius: BorderRadius.all(Radius.circular(2.0)),
240 241 242
        );
      case ButtonTextTheme.primary:
        return const RoundedRectangleBorder(
243
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
244 245 246 247 248 249
        );
    }
    return const RoundedRectangleBorder();
  }
  final ShapeBorder _shape;

250 251 252 253 254 255 256 257 258 259 260
  /// 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;

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
  /// Creates a copy of this button theme data object with the matching fields
  /// replaced with the non-null parameter values.
  ButtonThemeData copyWith({
    ButtonTextTheme textTheme,
    double minWidth,
    double height,
    EdgeInsetsGeometry padding,
    ShapeBorder shape,
    bool alignedDropdown,
  }) {
    return new ButtonThemeData(
      textTheme: textTheme ?? this.textTheme,
      minWidth: minWidth ?? this.minWidth,
      height: height ?? this.height,
      padding: padding ?? this.padding,
      shape: shape ?? this.shape,
      alignedDropdown: alignedDropdown ?? this.alignedDropdown,
    );
  }

281 282 283 284 285 286 287 288 289
  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final ButtonThemeData typedOther = other;
    return textTheme == typedOther.textTheme
        && minWidth == typedOther.minWidth
        && height == typedOther.height
        && padding == typedOther.padding
290 291
        && shape == typedOther.shape
        && alignedDropdown == typedOther.alignedDropdown;
292 293 294 295 296 297 298 299 300 301
  }

  @override
  int get hashCode {
    return hashValues(
      textTheme,
      minWidth,
      height,
      padding,
      shape,
302
      alignedDropdown,
303 304
    );
  }
305 306

  @override
307 308
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
309
    const ButtonThemeData defaultTheme = ButtonThemeData();
310 311 312 313 314 315
    properties.add(new EnumProperty<ButtonTextTheme>('textTheme', textTheme, defaultValue: defaultTheme.textTheme));
    properties.add(new DoubleProperty('minWidth', minWidth, defaultValue: defaultTheme.minWidth));
    properties.add(new DoubleProperty('height', height, defaultValue: defaultTheme.height));
    properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: defaultTheme.padding));
    properties.add(new DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultTheme.shape));
    properties.add(new FlagProperty('alignedDropdown',
316 317 318 319
      value: alignedDropdown,
      defaultValue: defaultTheme.alignedDropdown,
      ifTrue: 'dropdown width matches button',
    ));
320
  }
321
}