theme_data.dart 17.2 KB
Newer Older
1 2 3 4
// 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.

5
import 'dart:ui' show Color, hashValues;
6

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

9
import 'colors.dart';
Adam Barth's avatar
Adam Barth committed
10 11
import 'icon_theme_data.dart';
import 'typography.dart';
12

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/// Describes the contrast needs of a color.
enum Brightness {
  /// The color is dark and will require a light text color to achieve readable
  /// contrast.
  ///
  /// For example, the color might be dark grey, requiring white text.
  dark,

  /// The color is light and will require a dark text color to achieve readable
  /// contrast.
  ///
  /// For example, the color might be bright white, requiring black text.
  light,
}

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
// Deriving these values is black magic. The spec claims that pressed buttons
// have a highlight of 0x66999999, but that's clearly wrong. The videos in the
// spec show that buttons have a composited highlight of #E1E1E1 on a background
// of #FAFAFA. Assuming that the highlight really has an opacity of 0x66, we can
// solve for the actual color of the highlight:
const Color _kLightThemeHighlightColor = const Color(0x66BCBCBC);

// The same video shows the splash compositing to #D7D7D7 on a background of
// #E1E1E1. Again, assuming the splash has an opacity of 0x66, we can solve for
// the actual color of the splash:
const Color _kLightThemeSplashColor = const Color(0x66C8C8C8);

// Unfortunately, a similar video isn't available for the dark theme, which
// means we assume the values in the spec are actually correct.
const Color _kDarkThemeHighlightColor = const Color(0x40CCCCCC);
const Color _kDarkThemeSplashColor = const Color(0x40CCCCCC);

45 46 47
/// Holds the color and typography values for a material design theme.
///
/// Use this class to configure a [Theme] widget.
48
class ThemeData {
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
  /// Create a ThemeData given a set of preferred values.
  ///
  /// Default values will be derived for arguments that are omitted.
  ///
  /// The most useful values to give are, in order of importance:
  ///
  ///  * The desired theme [brightness].
  ///
  ///  * The primary color palette (the [primarySwatch]), chosen from
  ///    one of the swatches defined by the material design spec. This
  ///    should be one of the maps from the [Colors] class that do not
  ///    have "accent" in their name.
  ///
  ///  * The [accentColor], sometimes called the secondary color, and,
  ///    if the accent color is specified, its brightness
  ///    ([accentColorBrightness]), so that the right contrasting text
  ///    color will be used over the accent color.
  ///
  /// See <https://www.google.com/design/spec/style/color.html> for
  /// more discussion on how to pick the right colors.
69
  factory ThemeData({
70
    Brightness brightness,
71
    Map<int, Color> primarySwatch,
72
    Color primaryColor,
73
    Brightness primaryColorBrightness,
74
    Color accentColor,
75
    Brightness accentColorBrightness,
76 77 78 79 80
    Color canvasColor,
    Color cardColor,
    Color dividerColor,
    Color highlightColor,
    Color splashColor,
81 82
    Color selectedRowColor,
    Color unselectedWidgetColor,
83
    Color disabledColor,
84
    Color buttonColor,
85
    Color secondaryHeaderColor,
86
    Color textSelectionColor,
87
    Color textSelectionHandleColor,
88
    Color backgroundColor,
89 90
    Color indicatorColor,
    Color hintColor,
91
    Color errorColor,
92
    TextTheme textTheme,
93
    TextTheme primaryTextTheme,
Ian Hickson's avatar
Ian Hickson committed
94
    IconThemeData iconTheme,
95 96
    IconThemeData primaryIconTheme,
    TargetPlatform platform
97
  }) {
98 99
    brightness ??= Brightness.light;
    final bool isDark = brightness == Brightness.dark;
100 101
    primarySwatch ??= Colors.blue;
    primaryColor ??= isDark ? Colors.grey[900] : primarySwatch[500];
102
    primaryColorBrightness ??= Brightness.dark;
Ian Hickson's avatar
Ian Hickson committed
103
    final bool primaryIsDark = primaryColorBrightness == Brightness.dark;
Hans Muller's avatar
Hans Muller committed
104
    accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500];
105
    accentColorBrightness ??= Brightness.dark;
106 107 108 109 110
    canvasColor ??= isDark ? Colors.grey[850] : Colors.grey[50];
    cardColor ??= isDark ? Colors.grey[800] : Colors.white;
    dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
    highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
    splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
111 112
    selectedRowColor ??= Colors.grey[100];
    unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54;
113
    disabledColor ??= isDark ? Colors.white30 : Colors.black26;
114
    buttonColor ??= isDark ? primarySwatch[600] : Colors.grey[300];
115 116
    // Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess.
    secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50];
117
    textSelectionColor ??= isDark ? accentColor : primarySwatch[200];
118
    textSelectionHandleColor ??= isDark ? Colors.tealAccent[400] : primarySwatch[300];
119
    backgroundColor ??= isDark ? Colors.grey[700] : primarySwatch[200];
120 121
    indicatorColor ??= accentColor == primaryColor ? Colors.white : accentColor;
    hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000);
122
    errorColor ??= Colors.red[700];
123
    textTheme ??= isDark ? Typography.white : Typography.black;
Ian Hickson's avatar
Ian Hickson committed
124 125 126
    primaryTextTheme ??= primaryIsDark ? Typography.white : Typography.black;
    iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
    primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
127
    platform ??= defaultTargetPlatform;
128 129 130 131
    return new ThemeData.raw(
      brightness: brightness,
      primaryColor: primaryColor,
      primaryColorBrightness: primaryColorBrightness,
132 133
      accentColor: accentColor,
      accentColorBrightness: accentColorBrightness,
134 135 136 137 138
      canvasColor: canvasColor,
      cardColor: cardColor,
      dividerColor: dividerColor,
      highlightColor: highlightColor,
      splashColor: splashColor,
139 140
      selectedRowColor: selectedRowColor,
      unselectedWidgetColor: unselectedWidgetColor,
141
      disabledColor: disabledColor,
142
      buttonColor: buttonColor,
143
      secondaryHeaderColor: secondaryHeaderColor,
144
      textSelectionColor: textSelectionColor,
145
      textSelectionHandleColor: textSelectionHandleColor,
146
      backgroundColor: backgroundColor,
147 148
      indicatorColor: indicatorColor,
      hintColor: hintColor,
149
      errorColor: errorColor,
150
      textTheme: textTheme,
151
      primaryTextTheme: primaryTextTheme,
Ian Hickson's avatar
Ian Hickson committed
152
      iconTheme: iconTheme,
153 154
      primaryIconTheme: primaryIconTheme,
      platform: platform
155
    );
156 157
  }

158 159 160 161 162 163
  /// Create a ThemeData given a set of exact values. All the values
  /// must be specified.
  ///
  /// This will rarely be used directly. It is used by [lerp] to
  /// create intermediate themes based on two themes created with the
  /// [new ThemeData] constructor.
Ian Hickson's avatar
Ian Hickson committed
164 165 166 167 168 169 170 171 172 173 174
  ThemeData.raw({
    this.brightness,
    this.primaryColor,
    this.primaryColorBrightness,
    this.accentColor,
    this.accentColorBrightness,
    this.canvasColor,
    this.cardColor,
    this.dividerColor,
    this.highlightColor,
    this.splashColor,
175 176
    this.selectedRowColor,
    this.unselectedWidgetColor,
Ian Hickson's avatar
Ian Hickson committed
177 178
    this.disabledColor,
    this.buttonColor,
179
    this.secondaryHeaderColor,
180
    this.textSelectionColor,
181
    this.textSelectionHandleColor,
Ian Hickson's avatar
Ian Hickson committed
182 183 184 185
    this.backgroundColor,
    this.indicatorColor,
    this.hintColor,
    this.errorColor,
186
    this.textTheme,
Ian Hickson's avatar
Ian Hickson committed
187
    this.primaryTextTheme,
Ian Hickson's avatar
Ian Hickson committed
188
    this.iconTheme,
189 190
    this.primaryIconTheme,
    this.platform
Ian Hickson's avatar
Ian Hickson committed
191 192 193 194 195 196 197 198 199 200 201
  }) {
    assert(brightness != null);
    assert(primaryColor != null);
    assert(primaryColorBrightness != null);
    assert(accentColor != null);
    assert(accentColorBrightness != null);
    assert(canvasColor != null);
    assert(cardColor != null);
    assert(dividerColor != null);
    assert(highlightColor != null);
    assert(splashColor != null);
202 203
    assert(selectedRowColor != null);
    assert(unselectedWidgetColor != null);
Ian Hickson's avatar
Ian Hickson committed
204 205
    assert(disabledColor != null);
    assert(buttonColor != null);
206
    assert(secondaryHeaderColor != null);
207
    assert(textSelectionColor != null);
208
    assert(textSelectionHandleColor != null);
Ian Hickson's avatar
Ian Hickson committed
209 210 211 212
    assert(disabledColor != null);
    assert(indicatorColor != null);
    assert(hintColor != null);
    assert(errorColor != null);
213
    assert(textTheme != null);
Ian Hickson's avatar
Ian Hickson committed
214
    assert(primaryTextTheme != null);
Ian Hickson's avatar
Ian Hickson committed
215
    assert(iconTheme != null);
Ian Hickson's avatar
Ian Hickson committed
216
    assert(primaryIconTheme != null);
217
    assert(platform != null);
Ian Hickson's avatar
Ian Hickson committed
218 219
  }

220
  /// A default light blue theme.
221
  factory ThemeData.light() => new ThemeData(brightness: Brightness.light);
222 223

  /// A default dark theme with a teal accent color.
224
  factory ThemeData.dark() => new ThemeData(brightness: Brightness.dark);
225 226 227 228

  /// The default theme. Same as [new ThemeData.light].
  ///
  /// This is used by [Theme.of] when no theme has been specified.
229 230
  factory ThemeData.fallback() => new ThemeData.light();

231 232 233
  /// The brightness of the overall theme of the application. Used by widgets
  /// like buttons to determine what color to pick when not using the primary or
  /// accent color.
234
  ///
235 236
  /// When the [Brightness] is dark, the canvas, card, and primary colors are
  /// all dark. When the [Brightness] is light, the canvas and card colors
237 238
  /// are bright, and the primary color's darkness varies as described by
  /// primaryColorBrightness. The primaryColor does not contrast well with the
239
  /// card and canvas colors when the brightness is dark; when the brightness is
240
  /// dark, use Colors.white or the accentColor for a contrasting color.
241
  final Brightness brightness;
242

243
  /// The background color for major parts of the app (toolbars, tab bars, etc)
244
  final Color primaryColor;
245

246
  /// The brightness of the primaryColor. Used to determine the color of text and
247
  /// icons placed on top of the primary color (e.g. toolbar text).
248
  final Brightness primaryColorBrightness;
249

250
  /// The foreground color for widgets (knobs, text, etc)
251
  final Color accentColor;
252

253
  /// The brightness of the accentColor. Used to determine the color of text
254 255
  /// and icons placed on top of the accent color (e.g. the icons on a floating
  /// action button).
256
  final Brightness accentColorBrightness;
257

258 259
  /// The color of [Material] when it is of infinite extent, e.g. the
  /// body of a [Scaffold].
260
  final Color canvasColor;
261 262

  /// The color of [Material] when it is used as a [Card].
263
  final Color cardColor;
264 265 266

  /// The color of [Divider]s and [PopupMenuDivider]s, also used
  /// between [ListItem]s, between rows in [DataTable]s, and so forth.
267
  final Color dividerColor;
268 269 270

  /// The highlight color used during ink splash animations or to
  /// indicate an item in a menu is selected.
271
  final Color highlightColor;
272 273

  /// The color of ink splashes. See [InkWell].
274
  final Color splashColor;
275 276 277 278 279 280 281 282 283 284 285 286

  /// The color used to highlight selected rows.
  final Color selectedRowColor;

  /// The color used for widgets in their inactive (but enabled)
  /// state. For example, an unchecked checkbox. Usually contrasted
  /// with the [accentColor]. See also [disabledColor].
  final Color unselectedWidgetColor;

  /// The color used for widgets that are inoperative, regardless of
  /// their state. For example, a disabled checkbox (which may be
  /// checked or unchecked).
287
  final Color disabledColor;
288 289

  /// The default color of the [Material] used in [RaisedButton]s.
290
  final Color buttonColor;
291

292 293 294 295 296 297
  /// The color of the header of a [PaginatedDataTable] when there are selected rows.
  // According to the spec for data tables:
  // https://material.google.com/components/data-tables.html#data-tables-tables-within-cards
  // ...this should be the "50-value of secondary app color".
  final Color secondaryHeaderColor;

298 299 300
  /// The color of text selections in text fields, such as [Input].
  final Color textSelectionColor;

301
  /// The color of the handles used to adjust what part of the text is currently selected.
302 303
  final Color textSelectionHandleColor;

304 305
  /// A color that contrasts with the [primaryColor], e.g. used as the
  /// remaining part of a progress bar.
306 307
  final Color backgroundColor;

308
  /// The color of the selected tab indicator in a tab bar.
309 310
  final Color indicatorColor;

311 312
  /// The color to use for hint text or placeholder text, e.g. in
  /// [Input] fields.
313 314
  final Color hintColor;

315
  /// The color to use for input validation errors, e.g. in [Input] fields.
316 317
  final Color errorColor;

318
  /// Text with a color that contrasts with the card and canvas colors.
319
  final TextTheme textTheme;
320 321 322 323

  /// A text theme that contrasts with the primary color.
  final TextTheme primaryTextTheme;

Ian Hickson's avatar
Ian Hickson committed
324 325 326
  /// An icon theme that contrasts with the card and canvas colors.
  final IconThemeData iconTheme;

327
  /// An icon theme that contrasts with the primary color.
328 329
  final IconThemeData primaryIconTheme;

330 331 332 333 334
  /// The platform the material widgets should adapt to target.
  ///
  /// Defaults to the current platform.
  final TargetPlatform platform;

335
  /// Linearly interpolate between two themes.
336 337 338 339 340 341 342 343 344 345
  static ThemeData lerp(ThemeData begin, ThemeData end, double t) {
    return new ThemeData.raw(
      brightness: t < 0.5 ? begin.brightness : end.brightness,
      primaryColor: Color.lerp(begin.primaryColor, end.primaryColor, t),
      primaryColorBrightness: t < 0.5 ? begin.primaryColorBrightness : end.primaryColorBrightness,
      canvasColor: Color.lerp(begin.canvasColor, end.canvasColor, t),
      cardColor: Color.lerp(begin.cardColor, end.cardColor, t),
      dividerColor: Color.lerp(begin.dividerColor, end.dividerColor, t),
      highlightColor: Color.lerp(begin.highlightColor, end.highlightColor, t),
      splashColor: Color.lerp(begin.splashColor, end.splashColor, t),
346 347
      selectedRowColor: Color.lerp(begin.selectedRowColor, end.selectedRowColor, t),
      unselectedWidgetColor: Color.lerp(begin.unselectedWidgetColor, end.unselectedWidgetColor, t),
348
      disabledColor: Color.lerp(begin.disabledColor, end.disabledColor, t),
349
      buttonColor: Color.lerp(begin.buttonColor, end.buttonColor, t),
350
      secondaryHeaderColor: Color.lerp(begin.secondaryHeaderColor, end.secondaryHeaderColor, t),
351
      textSelectionColor: Color.lerp(begin.textSelectionColor, end.textSelectionColor, t),
352
      textSelectionHandleColor: Color.lerp(begin.textSelectionHandleColor, end.textSelectionHandleColor, t),
353
      backgroundColor: Color.lerp(begin.backgroundColor, end.backgroundColor, t),
354 355 356 357
      accentColor: Color.lerp(begin.accentColor, end.accentColor, t),
      accentColorBrightness: t < 0.5 ? begin.accentColorBrightness : end.accentColorBrightness,
      indicatorColor: Color.lerp(begin.indicatorColor, end.indicatorColor, t),
      hintColor: Color.lerp(begin.hintColor, end.hintColor, t),
358
      errorColor: Color.lerp(begin.errorColor, end.errorColor, t),
359
      textTheme: TextTheme.lerp(begin.textTheme, end.textTheme, t),
360
      primaryTextTheme: TextTheme.lerp(begin.primaryTextTheme, end.primaryTextTheme, t),
Ian Hickson's avatar
Ian Hickson committed
361
      iconTheme: IconThemeData.lerp(begin.iconTheme, end.iconTheme, t),
362 363
      primaryIconTheme: IconThemeData.lerp(begin.primaryIconTheme, end.primaryIconTheme, t),
      platform: t < 0.5 ? begin.platform : end.platform
364 365 366
    );
  }

367
  @override
368 369 370 371 372
  bool operator==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    ThemeData otherData = other;
    return (otherData.brightness == brightness) &&
373 374
           (otherData.primaryColor == primaryColor) &&
           (otherData.primaryColorBrightness == primaryColorBrightness) &&
375 376 377 378
           (otherData.canvasColor == canvasColor) &&
           (otherData.cardColor == cardColor) &&
           (otherData.dividerColor == dividerColor) &&
           (otherData.highlightColor == highlightColor) &&
379
           (otherData.splashColor == splashColor) &&
380 381
           (otherData.selectedRowColor == selectedRowColor) &&
           (otherData.unselectedWidgetColor == unselectedWidgetColor) &&
382
           (otherData.disabledColor == disabledColor) &&
383
           (otherData.buttonColor == buttonColor) &&
384
           (otherData.secondaryHeaderColor == secondaryHeaderColor) &&
385
           (otherData.textSelectionColor == textSelectionColor) &&
386
           (otherData.textSelectionHandleColor == textSelectionHandleColor) &&
387
           (otherData.backgroundColor == backgroundColor) &&
388 389 390 391
           (otherData.accentColor == accentColor) &&
           (otherData.accentColorBrightness == accentColorBrightness) &&
           (otherData.indicatorColor == indicatorColor) &&
           (otherData.hintColor == hintColor) &&
392
           (otherData.errorColor == errorColor) &&
393
           (otherData.textTheme == textTheme) &&
394
           (otherData.primaryTextTheme == primaryTextTheme) &&
Ian Hickson's avatar
Ian Hickson committed
395
           (otherData.iconTheme == iconTheme) &&
396
           (otherData.primaryIconTheme == primaryIconTheme);
397
  }
398 399

  @override
400
  int get hashCode {
401 402
    return hashValues(
      brightness,
403 404
      primaryColor,
      primaryColorBrightness,
405 406 407 408
      canvasColor,
      cardColor,
      dividerColor,
      highlightColor,
409
      splashColor,
410 411
      selectedRowColor,
      unselectedWidgetColor,
412
      disabledColor,
413
      buttonColor,
414
      secondaryHeaderColor,
415
      textSelectionColor,
416
      textSelectionHandleColor,
417
      backgroundColor,
418
      accentColor,
419
      accentColorBrightness,
420 421 422 423
      hashValues( // Too many values.
        indicatorColor,
        hintColor,
        errorColor,
424
        textTheme,
425
        primaryTextTheme,
Ian Hickson's avatar
Ian Hickson committed
426
        iconTheme,
427 428
        primaryIconTheme
      )
429
    );
430
  }
Hixie's avatar
Hixie committed
431

432
  @override
433
  String toString() => '$runtimeType($brightness $primaryColor etc...)';
434
}