theme_data.dart 56.5 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

xster's avatar
xster committed
7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/services.dart';
10
import 'package:flutter/widgets.dart';
11

12
import 'app_bar_theme.dart';
13
import 'bottom_app_bar_theme.dart';
14
import 'button_theme.dart';
15
import 'card_theme.dart';
16
import 'chip_theme.dart';
17
import 'color_scheme.dart';
18
import 'colors.dart';
19
import 'dialog_theme.dart';
20
import 'floating_action_button_theme.dart';
21 22
import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory;
23
import 'input_decorator.dart';
24
import 'page_transitions_theme.dart';
25
import 'slider_theme.dart';
26
import 'tab_bar_theme.dart';
27
import 'text_theme.dart';
Adam Barth's avatar
Adam Barth committed
28
import 'typography.dart';
29

30
export 'package:flutter/services.dart' show Brightness;
31

32 33 34 35 36
// 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:
37
const Color _kLightThemeHighlightColor = Color(0x66BCBCBC);
38 39 40 41

// 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:
42
const Color _kLightThemeSplashColor = Color(0x66C8C8C8);
43 44 45

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

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
/// Configures the tap target and layout size of certain Material widgets.
///
/// Changing the value in [ThemeData.materialTapTargetSize] will affect the
/// accessibility experience.
///
/// Some of the impacted widgets include:
///
///   * [FloatingActionButton], only the mini tap target size is increased.
///   * [MaterialButton]
///   * [OutlineButton]
///   * [FlatButton]
///   * [RaisedButton]
///   * [TimePicker]
///   * [SnackBar]
///   * [Chip]
///   * [RawChip]
///   * [InputChip]
///   * [ChoiceChip]
///   * [FilterChip]
///   * [ActionChip]
///   * [Radio]
///   * [Switch]
///   * [Checkbox]
enum MaterialTapTargetSize {
  /// Expands the minimum tap target size to 48px by 48px.
  ///
  /// This is the default value of [ThemeData.materialHitTestSize] and the
  /// recommended size to conform to Android accessibility scanner
  /// recommendations.
  padded,

  /// Shrinks the tap target size to the minimum provided by the Material
  /// specification.
  shrinkWrap,
}

85 86 87
/// Holds the color and typography values for a material design theme.
///
/// Use this class to configure a [Theme] widget.
88 89
///
/// To obtain the current theme, use [Theme.of].
90
@immutable
91 92
class ThemeData extends Diagnosticable {
  /// Create a [ThemeData] given a set of preferred values.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
  ///
  /// 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.
  ///
110
  /// See <https://material.io/design/color/> for
111
  /// more discussion on how to pick the right colors.
112
  factory ThemeData({
113
    Brightness brightness,
114
    MaterialColor primarySwatch,
115
    Color primaryColor,
116
    Brightness primaryColorBrightness,
117 118
    Color primaryColorLight,
    Color primaryColorDark,
119
    Color accentColor,
120
    Brightness accentColorBrightness,
121
    Color canvasColor,
122
    Color scaffoldBackgroundColor,
123
    Color bottomAppBarColor,
124 125 126 127
    Color cardColor,
    Color dividerColor,
    Color highlightColor,
    Color splashColor,
128
    InteractiveInkFeatureFactory splashFactory,
129 130
    Color selectedRowColor,
    Color unselectedWidgetColor,
131
    Color disabledColor,
132
    Color buttonColor,
133
    ButtonThemeData buttonTheme,
134
    Color secondaryHeaderColor,
135
    Color textSelectionColor,
136
    Color cursorColor,
137
    Color textSelectionHandleColor,
138
    Color backgroundColor,
139
    Color dialogBackgroundColor,
140 141
    Color indicatorColor,
    Color hintColor,
142
    Color errorColor,
143
    Color toggleableActiveColor,
144
    String fontFamily,
145
    TextTheme textTheme,
146
    TextTheme primaryTextTheme,
147
    TextTheme accentTextTheme,
148
    InputDecorationTheme inputDecorationTheme,
Ian Hickson's avatar
Ian Hickson committed
149
    IconThemeData iconTheme,
150
    IconThemeData primaryIconTheme,
151
    IconThemeData accentIconTheme,
152
    SliderThemeData sliderTheme,
153
    TabBarTheme tabBarTheme,
154
    CardTheme cardTheme,
155
    ChipThemeData chipTheme,
156
    TargetPlatform platform,
157
    MaterialTapTargetSize materialTapTargetSize,
158
    PageTransitionsTheme pageTransitionsTheme,
159
    AppBarTheme appBarTheme,
160
    BottomAppBarTheme bottomAppBarTheme,
161
    ColorScheme colorScheme,
162
    DialogTheme dialogTheme,
163
    FloatingActionButtonThemeData floatingActionButtonTheme,
164
    Typography typography,
165
    CupertinoThemeData cupertinoOverrideTheme,
166
  }) {
167 168
    brightness ??= Brightness.light;
    final bool isDark = brightness == Brightness.dark;
169
    primarySwatch ??= Colors.blue;
170
    primaryColor ??= isDark ? Colors.grey[900] : primarySwatch;
171
    primaryColorBrightness ??= estimateBrightnessForColor(primaryColor);
172 173
    primaryColorLight ??= isDark ? Colors.grey[500] : primarySwatch[100];
    primaryColorDark ??= isDark ? Colors.black : primarySwatch[700];
Ian Hickson's avatar
Ian Hickson committed
174
    final bool primaryIsDark = primaryColorBrightness == Brightness.dark;
175
    toggleableActiveColor ??= isDark ? Colors.tealAccent[200] : (accentColor ?? primarySwatch[600]);
Hans Muller's avatar
Hans Muller committed
176
    accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500];
177
    accentColorBrightness ??= estimateBrightnessForColor(accentColor);
178
    final bool accentIsDark = accentColorBrightness == Brightness.dark;
179
    canvasColor ??= isDark ? Colors.grey[850] : Colors.grey[50];
180
    scaffoldBackgroundColor ??= canvasColor;
181
    bottomAppBarColor ??= isDark ? Colors.grey[800] : Colors.white;
182 183
    cardColor ??= isDark ? Colors.grey[800] : Colors.white;
    dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
184 185 186 187 188 189 190 191 192 193 194 195 196

    // Create a ColorScheme that is backwards compatible as possible
    // with the existing default ThemeData color values.
    colorScheme ??= ColorScheme.fromSwatch(
      primarySwatch: primarySwatch,
      primaryColorDark: primaryColorDark,
      accentColor: accentColor,
      cardColor: cardColor,
      backgroundColor: backgroundColor,
      errorColor: errorColor,
      brightness: brightness,
    );

197
    splashFactory ??= InkSplash.splashFactory;
198 199
    selectedRowColor ??= Colors.grey[100];
    unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54;
200 201
    // Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess.
    secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50];
202
    textSelectionColor ??= isDark ? accentColor : primarySwatch[200];
203
    // todo (sandrasandeep): change to color provided by Material Design team
204
    cursorColor = cursorColor ?? const Color.fromRGBO(66, 133, 244, 1.0);
205
    textSelectionHandleColor ??= isDark ? Colors.tealAccent[400] : primarySwatch[300];
206
    backgroundColor ??= isDark ? Colors.grey[700] : primarySwatch[200];
207
    dialogBackgroundColor ??= isDark ? Colors.grey[800] : Colors.white;
208
    indicatorColor ??= accentColor == primaryColor ? Colors.white : accentColor;
209
    hintColor ??= isDark ?  const Color(0x80FFFFFF) : const Color(0x8A000000);
210
    errorColor ??= Colors.red[700];
211
    inputDecorationTheme ??= const InputDecorationTheme();
212
    pageTransitionsTheme ??= const PageTransitionsTheme();
213 214
    primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
    accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
215
    iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black87);
216
    platform ??= defaultTargetPlatform;
217
    typography ??= Typography(platform: platform);
218 219 220 221 222 223
    final TextTheme defaultTextTheme = isDark ? typography.white : typography.black;
    textTheme = defaultTextTheme.merge(textTheme);
    final TextTheme defaultPrimaryTextTheme = primaryIsDark ? typography.white : typography.black;
    primaryTextTheme = defaultPrimaryTextTheme.merge(primaryTextTheme);
    final TextTheme defaultAccentTextTheme = accentIsDark ? typography.white : typography.black;
    accentTextTheme = defaultAccentTextTheme.merge(accentTextTheme);
224
    materialTapTargetSize ??= MaterialTapTargetSize.padded;
225 226 227 228 229
    if (fontFamily != null) {
      textTheme = textTheme.apply(fontFamily: fontFamily);
      primaryTextTheme = primaryTextTheme.apply(fontFamily: fontFamily);
      accentTextTheme = accentTextTheme.apply(fontFamily: fontFamily);
    }
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

    // Used as the default color (fill color) for RaisedButtons. Computing the
    // default for ButtonThemeData for the sake of backwards compatibility.
    buttonColor ??= isDark ? primarySwatch[600] : Colors.grey[300];
    buttonTheme ??= ButtonThemeData(
      colorScheme: colorScheme,
      buttonColor: buttonColor,
      disabledColor: disabledColor,
      highlightColor: highlightColor,
      splashColor: splashColor,
      materialTapTargetSize: materialTapTargetSize,
    );
    disabledColor ??= isDark ? Colors.white30 : Colors.black38;
    highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
    splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;

246 247 248 249 250 251
    sliderTheme ??= SliderThemeData.fromPrimaryColors(
      primaryColor: primaryColor,
      primaryColorLight: primaryColorLight,
      primaryColorDark: primaryColorDark,
      valueIndicatorTextStyle: accentTextTheme.body2,
    );
252
    tabBarTheme ??= const TabBarTheme();
253
    appBarTheme ??= const AppBarTheme();
254
    bottomAppBarTheme ??= const BottomAppBarTheme();
255
    cardTheme ??= const CardTheme();
256
    chipTheme ??= ChipThemeData.fromDefaults(
257 258 259 260
      secondaryColor: primaryColor,
      brightness: brightness,
      labelStyle: textTheme.body2,
    );
261
    dialogTheme ??= const DialogTheme();
262
    floatingActionButtonTheme ??= const FloatingActionButtonThemeData();
xster's avatar
xster committed
263
    cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
264

265
    return ThemeData.raw(
266 267 268
      brightness: brightness,
      primaryColor: primaryColor,
      primaryColorBrightness: primaryColorBrightness,
269 270
      primaryColorLight: primaryColorLight,
      primaryColorDark: primaryColorDark,
271 272
      accentColor: accentColor,
      accentColorBrightness: accentColorBrightness,
273
      canvasColor: canvasColor,
274
      scaffoldBackgroundColor: scaffoldBackgroundColor,
275
      bottomAppBarColor: bottomAppBarColor,
276 277 278 279
      cardColor: cardColor,
      dividerColor: dividerColor,
      highlightColor: highlightColor,
      splashColor: splashColor,
280
      splashFactory: splashFactory,
281 282
      selectedRowColor: selectedRowColor,
      unselectedWidgetColor: unselectedWidgetColor,
283
      disabledColor: disabledColor,
284
      buttonTheme: buttonTheme,
285
      buttonColor: buttonColor,
286
      toggleableActiveColor: toggleableActiveColor,
287
      secondaryHeaderColor: secondaryHeaderColor,
288
      textSelectionColor: textSelectionColor,
289
      cursorColor: cursorColor,
290
      textSelectionHandleColor: textSelectionHandleColor,
291
      backgroundColor: backgroundColor,
292
      dialogBackgroundColor: dialogBackgroundColor,
293 294
      indicatorColor: indicatorColor,
      hintColor: hintColor,
295
      errorColor: errorColor,
296
      textTheme: textTheme,
297
      primaryTextTheme: primaryTextTheme,
298
      accentTextTheme: accentTextTheme,
299
      inputDecorationTheme: inputDecorationTheme,
Ian Hickson's avatar
Ian Hickson committed
300
      iconTheme: iconTheme,
301
      primaryIconTheme: primaryIconTheme,
302
      accentIconTheme: accentIconTheme,
303
      sliderTheme: sliderTheme,
304
      tabBarTheme: tabBarTheme,
305
      cardTheme: cardTheme,
306
      chipTheme: chipTheme,
307
      platform: platform,
308
      materialTapTargetSize: materialTapTargetSize,
309
      pageTransitionsTheme: pageTransitionsTheme,
310
      appBarTheme: appBarTheme,
311
      bottomAppBarTheme: bottomAppBarTheme,
312
      colorScheme: colorScheme,
313
      dialogTheme: dialogTheme,
314
      floatingActionButtonTheme: floatingActionButtonTheme,
315
      typography: typography,
xster's avatar
xster committed
316
      cupertinoOverrideTheme: cupertinoOverrideTheme,
317
    );
318 319
  }

xster's avatar
xster committed
320 321 322
  /// Create a [ThemeData] given a set of exact values. All the values must be
  /// specified. They all must also be non-null except for
  /// [cupertinoOverrideTheme].
323 324 325 326
  ///
  /// 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.
327
  const ThemeData.raw({
328 329 330
    // Warning: make sure these properties are in the exact same order as in
    // operator == and in the hashValues method and in the order of fields
    // in this class, and in the lerp() method.
331 332 333
    @required this.brightness,
    @required this.primaryColor,
    @required this.primaryColorBrightness,
334 335
    @required this.primaryColorLight,
    @required this.primaryColorDark,
336
    @required this.canvasColor,
337 338 339
    @required this.accentColor,
    @required this.accentColorBrightness,
    @required this.scaffoldBackgroundColor,
340
    @required this.bottomAppBarColor,
341 342 343 344
    @required this.cardColor,
    @required this.dividerColor,
    @required this.highlightColor,
    @required this.splashColor,
345
    @required this.splashFactory,
346 347 348
    @required this.selectedRowColor,
    @required this.unselectedWidgetColor,
    @required this.disabledColor,
349
    @required this.buttonTheme,
350
    @required this.buttonColor,
351 352
    @required this.secondaryHeaderColor,
    @required this.textSelectionColor,
353
    @required this.cursorColor,
354 355 356 357 358 359
    @required this.textSelectionHandleColor,
    @required this.backgroundColor,
    @required this.dialogBackgroundColor,
    @required this.indicatorColor,
    @required this.hintColor,
    @required this.errorColor,
360
    @required this.toggleableActiveColor,
361 362 363
    @required this.textTheme,
    @required this.primaryTextTheme,
    @required this.accentTextTheme,
364
    @required this.inputDecorationTheme,
365 366 367
    @required this.iconTheme,
    @required this.primaryIconTheme,
    @required this.accentIconTheme,
368
    @required this.sliderTheme,
369
    @required this.tabBarTheme,
370
    @required this.cardTheme,
371
    @required this.chipTheme,
372
    @required this.platform,
373
    @required this.materialTapTargetSize,
374
    @required this.pageTransitionsTheme,
375
    @required this.appBarTheme,
376
    @required this.bottomAppBarTheme,
377
    @required this.colorScheme,
378
    @required this.dialogTheme,
379
    @required this.floatingActionButtonTheme,
380
    @required this.typography,
xster's avatar
xster committed
381
    @required this.cupertinoOverrideTheme,
382 383 384
  }) : assert(brightness != null),
       assert(primaryColor != null),
       assert(primaryColorBrightness != null),
385 386
       assert(primaryColorLight != null),
       assert(primaryColorDark != null),
387 388 389 390
       assert(accentColor != null),
       assert(accentColorBrightness != null),
       assert(canvasColor != null),
       assert(scaffoldBackgroundColor != null),
391
       assert(bottomAppBarColor != null),
392 393 394 395
       assert(cardColor != null),
       assert(dividerColor != null),
       assert(highlightColor != null),
       assert(splashColor != null),
396
       assert(splashFactory != null),
397 398 399
       assert(selectedRowColor != null),
       assert(unselectedWidgetColor != null),
       assert(disabledColor != null),
400
       assert(toggleableActiveColor != null),
401
       assert(buttonTheme != null),
402 403
       assert(secondaryHeaderColor != null),
       assert(textSelectionColor != null),
404
       assert(cursorColor != null),
405 406 407 408 409 410 411 412 413
       assert(textSelectionHandleColor != null),
       assert(backgroundColor != null),
       assert(dialogBackgroundColor != null),
       assert(indicatorColor != null),
       assert(hintColor != null),
       assert(errorColor != null),
       assert(textTheme != null),
       assert(primaryTextTheme != null),
       assert(accentTextTheme != null),
414
       assert(inputDecorationTheme != null),
415 416 417
       assert(iconTheme != null),
       assert(primaryIconTheme != null),
       assert(accentIconTheme != null),
418
       assert(sliderTheme != null),
419
       assert(tabBarTheme != null),
420
       assert(cardTheme != null),
421
       assert(chipTheme != null),
422
       assert(platform != null),
423
       assert(materialTapTargetSize != null),
424
       assert(pageTransitionsTheme != null),
425
       assert(appBarTheme != null),
426
       assert(bottomAppBarTheme != null),
427
       assert(colorScheme != null),
428
       assert(dialogTheme != null),
429
       assert(floatingActionButtonTheme != null),
430 431 432 433 434
       assert(typography != null);

  // Warning: make sure these properties are in the exact same order as in
  // hashValues() and in the raw constructor and in the order of fields in
  // the class and in the lerp() method.
Ian Hickson's avatar
Ian Hickson committed
435

436
  /// A default light blue theme.
437 438 439
  ///
  /// This theme does not contain text geometry. Instead, it is expected that
  /// this theme is localized using text geometry using [ThemeData.localize].
440
  factory ThemeData.light() => ThemeData(brightness: Brightness.light);
441 442

  /// A default dark theme with a teal accent color.
443 444 445
  ///
  /// This theme does not contain text geometry. Instead, it is expected that
  /// this theme is localized using text geometry using [ThemeData.localize].
446
  factory ThemeData.dark() => ThemeData(brightness: Brightness.dark);
447

448
  /// The default color theme. Same as [new ThemeData.light].
449 450
  ///
  /// This is used by [Theme.of] when no theme has been specified.
451 452 453 454 455 456
  ///
  /// This theme does not contain text geometry. Instead, it is expected that
  /// this theme is localized using text geometry using [ThemeData.localize].
  ///
  /// Most applications would use [Theme.of], which provides correct localized
  /// text geometry.
457
  factory ThemeData.fallback() => ThemeData.light();
458

459 460 461
  /// 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.
462
  ///
463 464
  /// When the [Brightness] is dark, the canvas, card, and primary colors are
  /// all dark. When the [Brightness] is light, the canvas and card colors
465 466
  /// are bright, and the primary color's darkness varies as described by
  /// primaryColorBrightness. The primaryColor does not contrast well with the
467
  /// card and canvas colors when the brightness is dark; when the brightness is
468
  /// dark, use Colors.white or the accentColor for a contrasting color.
469
  final Brightness brightness;
470

471
  /// The background color for major parts of the app (toolbars, tab bars, etc)
472
  final Color primaryColor;
473

474
  /// The brightness of the [primaryColor]. Used to determine the color of text and
475
  /// icons placed on top of the primary color (e.g. toolbar text).
476
  final Brightness primaryColorBrightness;
477

478 479 480 481 482 483
  /// A lighter version of the [primaryColor].
  final Color primaryColorLight;

  /// A darker version of the [primaryColor].
  final Color primaryColorDark;

484 485 486
  /// The default color of [MaterialType.canvas] [Material].
  final Color canvasColor;

487
  /// The foreground color for widgets (knobs, text, overscroll edge effect, etc).
488
  final Color accentColor;
489

490
  /// The brightness of the [accentColor]. Used to determine the color of text
491 492
  /// and icons placed on top of the accent color (e.g. the icons on a floating
  /// action button).
493
  final Brightness accentColorBrightness;
494

495 496 497 498
  /// The default color of the [Material] that underlies the [Scaffold]. The
  /// background color for a typical material app or a page within the app.
  final Color scaffoldBackgroundColor;

499 500
  /// The default color of the [BottomAppBar].
  ///
Josh Soref's avatar
Josh Soref committed
501
  /// This can be overridden by specifying [BottomAppBar.color].
502 503
  final Color bottomAppBarColor;

504
  /// The color of [Material] when it is used as a [Card].
505
  final Color cardColor;
506 507

  /// The color of [Divider]s and [PopupMenuDivider]s, also used
508
  /// between [ListTile]s, between rows in [DataTable]s, and so forth.
509 510 511
  ///
  /// To create an appropriate [BorderSide] that uses this color, consider
  /// [Divider.createBorderSide].
512
  final Color dividerColor;
513 514 515

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

  /// The color of ink splashes. See [InkWell].
519
  final Color splashColor;
520

521 522 523 524 525 526 527
  /// Defines the appearance of ink splashes produces by [InkWell]
  /// and [InkResponse].
  ///
  /// See also:
  ///
  ///  * [InkSplash.splashFactory], which defines the default splash.
  ///  * [InkRipple.splashFactory], which defines a splash that spreads out
528
  ///    more aggressively than the default.
529 530
  final InteractiveInkFeatureFactory splashFactory;

531 532 533 534 535 536 537 538 539 540 541
  /// 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).
542
  final Color disabledColor;
543

544 545 546 547
  /// Defines the default configuration of button widgets, like [RaisedButton]
  /// and [FlatButton].
  final ButtonThemeData buttonTheme;

548 549 550
  /// The default fill color of the [Material] used in [RaisedButton]s.
  final Color buttonColor;

551 552
  /// The color of the header of a [PaginatedDataTable] when there are selected rows.
  // According to the spec for data tables:
553
  // https://material.io/archive/guidelines/components/data-tables.html#data-tables-tables-within-cards
554 555 556
  // ...this should be the "50-value of secondary app color".
  final Color secondaryHeaderColor;

557
  /// The color of text selections in text fields, such as [TextField].
558 559
  final Color textSelectionColor;

560 561 562
  /// The color of cursors in Material-style text fields, such as [TextField].
  final Color cursorColor;

563
  /// The color of the handles used to adjust what part of the text is currently selected.
564 565
  final Color textSelectionHandleColor;

566 567
  /// A color that contrasts with the [primaryColor], e.g. used as the
  /// remaining part of a progress bar.
568 569
  final Color backgroundColor;

570 571
  /// The background color of [Dialog] elements.
  final Color dialogBackgroundColor;
572

573
  /// The color of the selected tab indicator in a tab bar.
574 575
  final Color indicatorColor;

576
  /// The color to use for hint text or placeholder text, e.g. in
577
  /// [TextField] fields.
578 579
  final Color hintColor;

580
  /// The color to use for input validation errors, e.g. in [TextField] fields.
581 582
  final Color errorColor;

583 584 585 586
  /// The color used to highlight the active states of toggleable widgets like
  /// [Switch], [Radio], and [Checkbox].
  final Color toggleableActiveColor;

587
  /// Text with a color that contrasts with the card and canvas colors.
588
  final TextTheme textTheme;
589 590 591 592

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

593 594 595
  /// A text theme that contrasts with the accent color.
  final TextTheme accentTextTheme;

596 597 598 599 600 601
  /// The default [InputDecoration] values for [InputDecorator], [TextField],
  /// and [TextFormField] are based on this theme.
  ///
  /// See [InputDecoration.applyDefaults].
  final InputDecorationTheme inputDecorationTheme;

Ian Hickson's avatar
Ian Hickson committed
602 603 604
  /// An icon theme that contrasts with the card and canvas colors.
  final IconThemeData iconTheme;

605
  /// An icon theme that contrasts with the primary color.
606 607
  final IconThemeData primaryIconTheme;

608 609 610
  /// An icon theme that contrasts with the accent color.
  final IconThemeData accentIconTheme;

611 612 613 614 615
  /// The colors and shapes used to render [Slider].
  ///
  /// This is the value returned from [SliderTheme.of].
  final SliderThemeData sliderTheme;

616 617 618
  /// A theme for customizing the size, shape, and color of the tab bar indicator.
  final TabBarTheme tabBarTheme;

619 620 621 622 623
  /// The colors and styles used to render [Card].
  ///
  /// This is the value returned from [CardTheme.of].
  final CardTheme cardTheme;

624 625 626 627 628
  /// The colors and styles used to render [Chip], [
  ///
  /// This is the value returned from [ChipTheme.of].
  final ChipThemeData chipTheme;

629 630
  /// The platform the material widgets should adapt to target.
  ///
631 632 633 634 635 636 637 638
  /// Defaults to the current platform. This should be used in order to style UI
  /// elements according to platform conventions.
  ///
  /// [Platform.defaultTargetPlatform] should be used directly instead only in
  /// rare cases where it's necessary to determine behavior based on the
  /// platform. [dart.io.Platform.environment] should be used when it's critical
  /// to actually know the current platform, without any overrides possible (for
  /// example, when a system API is about to be called).
639 640
  final TargetPlatform platform;

641 642 643
  /// Configures the hit test size of certain Material widgets.
  final MaterialTapTargetSize materialTapTargetSize;

644 645 646 647 648 649 650
  /// Default [MaterialPageRoute] transitions per [TargetPlatform].
  ///
  /// [MaterialPageRoute.buildTransitions] delegates to a [PageTransitionsBuilder]
  /// whose [PageTransitionsBuilder.platform] matches [platform]. If a matching
  /// builder is not found, a builder whose platform is null is used.
  final PageTransitionsTheme pageTransitionsTheme;

651 652 653 654
  /// A theme for customizing the color, elevation, brightness, iconTheme and
  /// textTheme of [AppBar]s.
  final AppBarTheme appBarTheme;

655 656 657
  /// A theme for customizing the shape, elevation, and color of a [BottomAppBar].
  final BottomAppBarTheme bottomAppBarTheme;

658 659 660 661 662 663 664 665 666 667
  /// A set of thirteen colors that can be used to configure the
  /// color properties of most components.
  ///
  /// This property was added much later than the theme's set of highly
  /// specific colors, like [cardColor], [buttonColor], [canvasColor] etc.
  /// New components can be defined exclusively in terms of [colorScheme].
  /// Existing components will gradually migrate to it, to the extent
  /// that is possible without significant backwards compatibility breaks.
  final ColorScheme colorScheme;

668 669 670
  /// A theme for customizing the shape of a dialog.
  final DialogTheme dialogTheme;

671 672 673 674
  /// A theme for customizing the shape, elevation, and color of a
  /// [FloatingActionButton].
  final FloatingActionButtonThemeData floatingActionButtonTheme;

675 676 677 678
  /// The color and geometry [TextTheme] values used to configure [textTheme],
  /// [primaryTextTheme], and [accentTextTheme].
  final Typography typography;

xster's avatar
xster committed
679 680 681 682 683 684 685 686 687 688 689 690
  /// Components of the [CupertinoThemeData] to override from the Material
  /// [ThemeData] adaptation.
  ///
  /// By default, [cupertinoOverrideTheme] is null and Cupertino widgets
  /// descendant to the Material [Theme] will adhere to a [CupertinoTheme]
  /// derived from the Material [ThemeData]. e.g. [ThemeData]'s [ColorTheme]
  /// will also inform the [CupertinoThemeData]'s `primaryColor` etc.
  ///
  /// This cascading effect for individual attributes of the [CupertinoThemeData]
  /// can be overridden using attributes of this [cupertinoOverrideTheme].
  final CupertinoThemeData cupertinoOverrideTheme;

691 692 693 694 695
  /// Creates a copy of this theme but with the given fields replaced with the new values.
  ThemeData copyWith({
    Brightness brightness,
    Color primaryColor,
    Brightness primaryColorBrightness,
696 697
    Color primaryColorLight,
    Color primaryColorDark,
698 699 700
    Color accentColor,
    Brightness accentColorBrightness,
    Color canvasColor,
701
    Color scaffoldBackgroundColor,
702
    Color bottomAppBarColor,
703 704 705 706
    Color cardColor,
    Color dividerColor,
    Color highlightColor,
    Color splashColor,
707
    InteractiveInkFeatureFactory splashFactory,
708 709 710
    Color selectedRowColor,
    Color unselectedWidgetColor,
    Color disabledColor,
711
    ButtonThemeData buttonTheme,
712
    Color buttonColor,
713 714
    Color secondaryHeaderColor,
    Color textSelectionColor,
715
    Color cursorColor,
716 717
    Color textSelectionHandleColor,
    Color backgroundColor,
718
    Color dialogBackgroundColor,
719 720 721
    Color indicatorColor,
    Color hintColor,
    Color errorColor,
722
    Color toggleableActiveColor,
723 724
    TextTheme textTheme,
    TextTheme primaryTextTheme,
725
    TextTheme accentTextTheme,
726
    InputDecorationTheme inputDecorationTheme,
727 728
    IconThemeData iconTheme,
    IconThemeData primaryIconTheme,
729
    IconThemeData accentIconTheme,
730
    SliderThemeData sliderTheme,
731
    TabBarTheme tabBarTheme,
732
    CardTheme cardTheme,
733
    ChipThemeData chipTheme,
734
    TargetPlatform platform,
735
    MaterialTapTargetSize materialTapTargetSize,
736
    PageTransitionsTheme pageTransitionsTheme,
737
    AppBarTheme appBarTheme,
738
    BottomAppBarTheme bottomAppBarTheme,
739
    ColorScheme colorScheme,
740
    DialogTheme dialogTheme,
741
    FloatingActionButtonThemeData floatingActionButtonTheme,
742
    Typography typography,
xster's avatar
xster committed
743
    CupertinoThemeData cupertinoOverrideTheme,
744
  }) {
xster's avatar
xster committed
745
    cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
746
    return ThemeData.raw(
747 748 749
      brightness: brightness ?? this.brightness,
      primaryColor: primaryColor ?? this.primaryColor,
      primaryColorBrightness: primaryColorBrightness ?? this.primaryColorBrightness,
750 751
      primaryColorLight: primaryColorLight ?? this.primaryColorLight,
      primaryColorDark: primaryColorDark ?? this.primaryColorDark,
752 753 754 755
      accentColor: accentColor ?? this.accentColor,
      accentColorBrightness: accentColorBrightness ?? this.accentColorBrightness,
      canvasColor: canvasColor ?? this.canvasColor,
      scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
756
      bottomAppBarColor: bottomAppBarColor ?? this.bottomAppBarColor,
757 758 759 760
      cardColor: cardColor ?? this.cardColor,
      dividerColor: dividerColor ?? this.dividerColor,
      highlightColor: highlightColor ?? this.highlightColor,
      splashColor: splashColor ?? this.splashColor,
761
      splashFactory: splashFactory ?? this.splashFactory,
762 763 764 765
      selectedRowColor: selectedRowColor ?? this.selectedRowColor,
      unselectedWidgetColor: unselectedWidgetColor ?? this.unselectedWidgetColor,
      disabledColor: disabledColor ?? this.disabledColor,
      buttonColor: buttonColor ?? this.buttonColor,
766
      buttonTheme: buttonTheme ?? this.buttonTheme,
767 768
      secondaryHeaderColor: secondaryHeaderColor ?? this.secondaryHeaderColor,
      textSelectionColor: textSelectionColor ?? this.textSelectionColor,
769
      cursorColor: cursorColor ?? this.cursorColor,
770 771 772 773 774 775
      textSelectionHandleColor: textSelectionHandleColor ?? this.textSelectionHandleColor,
      backgroundColor: backgroundColor ?? this.backgroundColor,
      dialogBackgroundColor: dialogBackgroundColor ?? this.dialogBackgroundColor,
      indicatorColor: indicatorColor ?? this.indicatorColor,
      hintColor: hintColor ?? this.hintColor,
      errorColor: errorColor ?? this.errorColor,
776
      toggleableActiveColor: toggleableActiveColor ?? this.toggleableActiveColor,
777 778 779
      textTheme: textTheme ?? this.textTheme,
      primaryTextTheme: primaryTextTheme ?? this.primaryTextTheme,
      accentTextTheme: accentTextTheme ?? this.accentTextTheme,
780
      inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
781 782 783
      iconTheme: iconTheme ?? this.iconTheme,
      primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
      accentIconTheme: accentIconTheme ?? this.accentIconTheme,
784
      sliderTheme: sliderTheme ?? this.sliderTheme,
785
      tabBarTheme: tabBarTheme ?? this.tabBarTheme,
786
      cardTheme: cardTheme ?? this.cardTheme,
787
      chipTheme: chipTheme ?? this.chipTheme,
788
      platform: platform ?? this.platform,
789
      materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
790
      pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
791
      appBarTheme: appBarTheme ?? this.appBarTheme,
792
      bottomAppBarTheme: bottomAppBarTheme ?? this.bottomAppBarTheme,
793
      colorScheme: colorScheme ?? this.colorScheme,
794
      dialogTheme: dialogTheme ?? this.dialogTheme,
795
      floatingActionButtonTheme: floatingActionButtonTheme ?? this.floatingActionButtonTheme,
796
      typography: typography ?? this.typography,
xster's avatar
xster committed
797
      cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
798 799 800
    );
  }

801 802 803 804 805 806 807
  // The number 5 was chosen without any real science or research behind it. It
  // just seemed like a number that's not too big (we should be able to fit 5
  // copies of ThemeData in memory comfortably) and not too small (most apps
  // shouldn't have more than 5 theme/localization pairs).
  static const int _localizedThemeDataCacheSize = 5;

  /// Caches localized themes to speed up the [localize] method.
808
  static final _FifoCache<_IdentityThemeDataCacheKey, ThemeData> _localizedThemeDataCache =
809
      _FifoCache<_IdentityThemeDataCacheKey, ThemeData>(_localizedThemeDataCacheSize);
810

811 812
  /// Returns a new theme built by merging the text geometry provided by the
  /// [localTextGeometry] theme with the [baseTheme].
813
  ///
814 815 816 817
  /// For those text styles in the [baseTheme] whose [TextStyle.inherit] is set
  /// to true, the returned theme's text styles inherit the geometric properties
  /// of [localTextGeometry]. The resulting text styles' [TextStyle.inherit] is
  /// set to those provided by [localTextGeometry].
818
  static ThemeData localize(ThemeData baseTheme, TextTheme localTextGeometry) {
819 820 821 822 823 824 825 826 827 828 829 830
    // WARNING: this method memoizes the result in a cache based on the
    // previously seen baseTheme and localTextGeometry. Memoization is safe
    // because all inputs and outputs of this function are deeply immutable, and
    // the computations are referentially transparent. It only short-circuits
    // the computation if the new inputs are identical() to the previous ones.
    // It does not use the == operator, which performs a costly deep comparison.
    //
    // When changing this method, make sure the memoization logic is correct.
    // Remember:
    //
    // There are only two hard things in Computer Science: cache invalidation
    // and naming things. -- Phil Karlton
831 832
    assert(baseTheme != null);
    assert(localTextGeometry != null);
833 834

    return _localizedThemeDataCache.putIfAbsent(
835
      _IdentityThemeDataCacheKey(baseTheme, localTextGeometry),
836 837 838 839 840 841 842 843
      () {
        return baseTheme.copyWith(
          primaryTextTheme: localTextGeometry.merge(baseTheme.primaryTextTheme),
          accentTextTheme: localTextGeometry.merge(baseTheme.accentTextTheme),
          textTheme: localTextGeometry.merge(baseTheme.textTheme),
        );
      },
    );
844 845
  }

846 847 848 849 850 851
  /// Determines whether the given [Color] is [Brightness.light] or
  /// [Brightness.dark].
  ///
  /// This compares the luminosity of the given color to a threshold value that
  /// matches the material design specification.
  static Brightness estimateBrightnessForColor(Color color) {
852
    final double relativeLuminance = color.computeLuminance();
853 854 855 856 857 858

    // See <https://www.w3.org/TR/WCAG20/#contrast-ratiodef>
    // The spec says to use kThreshold=0.0525, but Material Design appears to bias
    // more towards using light text than WCAG20 recommends. Material Design spec
    // doesn't say what value to use, but 0.15 seemed close to what the Material
    // Design spec shows for its color palette on
859
    // <https://material.io/go/design-theming#color-color-palette>.
860
    const double kThreshold = 0.15;
861
    if ((relativeLuminance + 0.05) * (relativeLuminance + 0.05) > kThreshold)
862 863 864 865
      return Brightness.light;
    return Brightness.dark;
  }

866
  /// Linearly interpolate between two themes.
867
  ///
868
  /// The arguments must not be null.
869
  ///
870
  /// {@macro dart.ui.shadow.lerp}
871 872 873 874
  static ThemeData lerp(ThemeData a, ThemeData b, double t) {
    assert(a != null);
    assert(b != null);
    assert(t != null);
875 876 877
    // Warning: make sure these properties are in the exact same order as in
    // hashValues() and in the raw constructor and in the order of fields in
    // the class and in the lerp() method.
878
    return ThemeData.raw(
879 880 881
      brightness: t < 0.5 ? a.brightness : b.brightness,
      primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t),
      primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness,
882 883
      primaryColorLight: Color.lerp(a.primaryColorLight, b.primaryColorLight, t),
      primaryColorDark: Color.lerp(a.primaryColorDark, b.primaryColorDark, t),
884
      canvasColor: Color.lerp(a.canvasColor, b.canvasColor, t),
885 886
      accentColor: Color.lerp(a.accentColor, b.accentColor, t),
      accentColorBrightness: t < 0.5 ? a.accentColorBrightness : b.accentColorBrightness,
887
      scaffoldBackgroundColor: Color.lerp(a.scaffoldBackgroundColor, b.scaffoldBackgroundColor, t),
888
      bottomAppBarColor: Color.lerp(a.bottomAppBarColor, b.bottomAppBarColor, t),
889 890 891 892
      cardColor: Color.lerp(a.cardColor, b.cardColor, t),
      dividerColor: Color.lerp(a.dividerColor, b.dividerColor, t),
      highlightColor: Color.lerp(a.highlightColor, b.highlightColor, t),
      splashColor: Color.lerp(a.splashColor, b.splashColor, t),
893
      splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
894 895 896
      selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t),
      unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t),
      disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t),
897
      buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
898
      buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
899 900
      secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t),
      textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t),
901
      cursorColor: Color.lerp(a.cursorColor, b.cursorColor, t),
902 903 904 905 906 907
      textSelectionHandleColor: Color.lerp(a.textSelectionHandleColor, b.textSelectionHandleColor, t),
      backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
      dialogBackgroundColor: Color.lerp(a.dialogBackgroundColor, b.dialogBackgroundColor, t),
      indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
      hintColor: Color.lerp(a.hintColor, b.hintColor, t),
      errorColor: Color.lerp(a.errorColor, b.errorColor, t),
908
      toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t),
909 910 911
      textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
      primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t),
      accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
912
      inputDecorationTheme: t < 0.5 ? a.inputDecorationTheme : b.inputDecorationTheme,
913 914 915
      iconTheme: IconThemeData.lerp(a.iconTheme, b.iconTheme, t),
      primaryIconTheme: IconThemeData.lerp(a.primaryIconTheme, b.primaryIconTheme, t),
      accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
916
      sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
917
      tabBarTheme: TabBarTheme.lerp(a.tabBarTheme, b.tabBarTheme, t),
918
      cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t),
919
      chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t),
920
      platform: t < 0.5 ? a.platform : b.platform,
921
      materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
922
      pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
923
      appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
924
      bottomAppBarTheme: BottomAppBarTheme.lerp(a.bottomAppBarTheme, b.bottomAppBarTheme, t),
925
      colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
926
      dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
927
      floatingActionButtonTheme: FloatingActionButtonThemeData.lerp(a.floatingActionButtonTheme, b.floatingActionButtonTheme, t),
928
      typography: Typography.lerp(a.typography, b.typography, t),
xster's avatar
xster committed
929
      cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
930 931 932
    );
  }

933
  @override
934
  bool operator ==(Object other) {
935 936
    if (other.runtimeType != runtimeType)
      return false;
937
    final ThemeData otherData = other;
938 939 940
    // Warning: make sure these properties are in the exact same order as in
    // hashValues() and in the raw constructor and in the order of fields in
    // the class and in the lerp() method.
941
    return (otherData.brightness == brightness) &&
942 943
           (otherData.primaryColor == primaryColor) &&
           (otherData.primaryColorBrightness == primaryColorBrightness) &&
944 945 946 947
           (otherData.primaryColorLight == primaryColorLight) &&
           (otherData.primaryColorDark == primaryColorDark) &&
           (otherData.accentColor == accentColor) &&
           (otherData.accentColorBrightness == accentColorBrightness) &&
948
           (otherData.canvasColor == canvasColor) &&
949
           (otherData.scaffoldBackgroundColor == scaffoldBackgroundColor) &&
950
           (otherData.bottomAppBarColor == bottomAppBarColor) &&
951 952 953
           (otherData.cardColor == cardColor) &&
           (otherData.dividerColor == dividerColor) &&
           (otherData.highlightColor == highlightColor) &&
954
           (otherData.splashColor == splashColor) &&
955
           (otherData.splashFactory == splashFactory) &&
956 957
           (otherData.selectedRowColor == selectedRowColor) &&
           (otherData.unselectedWidgetColor == unselectedWidgetColor) &&
958
           (otherData.disabledColor == disabledColor) &&
959
           (otherData.buttonTheme == buttonTheme) &&
960
           (otherData.buttonColor == buttonColor) &&
961
           (otherData.secondaryHeaderColor == secondaryHeaderColor) &&
962
           (otherData.textSelectionColor == textSelectionColor) &&
963
           (otherData.cursorColor == cursorColor) &&
964
           (otherData.textSelectionHandleColor == textSelectionHandleColor) &&
965
           (otherData.backgroundColor == backgroundColor) &&
966
           (otherData.dialogBackgroundColor == dialogBackgroundColor) &&
967 968
           (otherData.indicatorColor == indicatorColor) &&
           (otherData.hintColor == hintColor) &&
969
           (otherData.errorColor == errorColor) &&
970
           (otherData.toggleableActiveColor == toggleableActiveColor) &&
971
           (otherData.textTheme == textTheme) &&
972
           (otherData.primaryTextTheme == primaryTextTheme) &&
973
           (otherData.accentTextTheme == accentTextTheme) &&
974
           (otherData.inputDecorationTheme == inputDecorationTheme) &&
Ian Hickson's avatar
Ian Hickson committed
975
           (otherData.iconTheme == iconTheme) &&
976
           (otherData.primaryIconTheme == primaryIconTheme) &&
977
           (otherData.accentIconTheme == accentIconTheme) &&
978
           (otherData.sliderTheme == sliderTheme) &&
979
           (otherData.tabBarTheme == tabBarTheme) &&
980
           (otherData.cardTheme == cardTheme) &&
981
           (otherData.chipTheme == chipTheme) &&
982
           (otherData.platform == platform) &&
983
           (otherData.materialTapTargetSize == materialTapTargetSize) &&
984
           (otherData.pageTransitionsTheme == pageTransitionsTheme) &&
985
           (otherData.appBarTheme == appBarTheme) &&
986
           (otherData.bottomAppBarTheme == bottomAppBarTheme) &&
987
           (otherData.colorScheme == colorScheme) &&
988
           (otherData.dialogTheme == dialogTheme) &&
989
           (otherData.floatingActionButtonTheme == floatingActionButtonTheme) &&
xster's avatar
xster committed
990 991
           (otherData.typography == typography) &&
           (otherData.cupertinoOverrideTheme == cupertinoOverrideTheme);
992
  }
993 994

  @override
995
  int get hashCode {
996
    // The hashValues() function supports up to 20 arguments.
997
    return hashValues(
998 999 1000
      // Warning: make sure these properties are in the exact same order as in
      // operator == and in the raw constructor and in the order of fields in
      // the class and in the lerp() method.
1001 1002 1003
      brightness,
      primaryColor,
      primaryColorBrightness,
1004 1005 1006 1007
      primaryColorLight,
      primaryColorDark,
      accentColor,
      accentColorBrightness,
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
      canvasColor,
      scaffoldBackgroundColor,
      bottomAppBarColor,
      cardColor,
      dividerColor,
      highlightColor,
      splashColor,
      splashFactory,
      selectedRowColor,
      unselectedWidgetColor,
1018
      disabledColor,
1019
      buttonTheme,
1020
      hashValues(
1021 1022
        buttonColor,
        toggleableActiveColor,
1023 1024 1025 1026
        secondaryHeaderColor,
        textSelectionColor,
        cursorColor,
        textSelectionHandleColor,
1027 1028
        backgroundColor,
        dialogBackgroundColor,
1029
        indicatorColor,
1030 1031 1032 1033 1034 1035
        hintColor,
        errorColor,
        textTheme,
        primaryTextTheme,
        accentTextTheme,
        inputDecorationTheme,
1036
        iconTheme,
1037 1038 1039
        primaryIconTheme,
        accentIconTheme,
        sliderTheme,
1040
        hashValues(
1041
          tabBarTheme,
1042
          cardTheme,
1043
          chipTheme,
1044 1045 1046
          platform,
          materialTapTargetSize,
          pageTransitionsTheme,
1047
          appBarTheme,
1048
          bottomAppBarTheme,
1049
          colorScheme,
1050
          dialogTheme,
1051
          floatingActionButtonTheme,
1052
          typography,
xster's avatar
xster committed
1053
          cupertinoOverrideTheme,
1054
        ),
1055
      ),
1056
    );
1057
  }
Hixie's avatar
Hixie committed
1058

1059
  @override
1060 1061
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
    final ThemeData defaultData = ThemeData.fallback();
    properties.add(EnumProperty<TargetPlatform>('platform', platform, defaultValue: defaultTargetPlatform));
    properties.add(EnumProperty<Brightness>('brightness', brightness, defaultValue: defaultData.brightness));
    properties.add(DiagnosticsProperty<Color>('primaryColor', primaryColor, defaultValue: defaultData.primaryColor));
    properties.add(EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness));
    properties.add(DiagnosticsProperty<Color>('accentColor', accentColor, defaultValue: defaultData.accentColor));
    properties.add(EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness, defaultValue: defaultData.accentColorBrightness));
    properties.add(DiagnosticsProperty<Color>('canvasColor', canvasColor, defaultValue: defaultData.canvasColor));
    properties.add(DiagnosticsProperty<Color>('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor));
    properties.add(DiagnosticsProperty<Color>('bottomAppBarColor', bottomAppBarColor, defaultValue: defaultData.bottomAppBarColor));
    properties.add(DiagnosticsProperty<Color>('cardColor', cardColor, defaultValue: defaultData.cardColor));
    properties.add(DiagnosticsProperty<Color>('dividerColor', dividerColor, defaultValue: defaultData.dividerColor));
    properties.add(DiagnosticsProperty<Color>('highlightColor', highlightColor, defaultValue: defaultData.highlightColor));
    properties.add(DiagnosticsProperty<Color>('splashColor', splashColor, defaultValue: defaultData.splashColor));
    properties.add(DiagnosticsProperty<Color>('selectedRowColor', selectedRowColor, defaultValue: defaultData.selectedRowColor));
    properties.add(DiagnosticsProperty<Color>('unselectedWidgetColor', unselectedWidgetColor, defaultValue: defaultData.unselectedWidgetColor));
    properties.add(DiagnosticsProperty<Color>('disabledColor', disabledColor, defaultValue: defaultData.disabledColor));
    properties.add(DiagnosticsProperty<Color>('buttonColor', buttonColor, defaultValue: defaultData.buttonColor));
    properties.add(DiagnosticsProperty<Color>('secondaryHeaderColor', secondaryHeaderColor, defaultValue: defaultData.secondaryHeaderColor));
    properties.add(DiagnosticsProperty<Color>('textSelectionColor', textSelectionColor, defaultValue: defaultData.textSelectionColor));
    properties.add(DiagnosticsProperty<Color>('cursorColor', cursorColor, defaultValue: defaultData.cursorColor));
    properties.add(DiagnosticsProperty<Color>('textSelectionHandleColor', textSelectionHandleColor, defaultValue: defaultData.textSelectionHandleColor));
    properties.add(DiagnosticsProperty<Color>('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor));
    properties.add(DiagnosticsProperty<Color>('dialogBackgroundColor', dialogBackgroundColor, defaultValue: defaultData.dialogBackgroundColor));
    properties.add(DiagnosticsProperty<Color>('indicatorColor', indicatorColor, defaultValue: defaultData.indicatorColor));
    properties.add(DiagnosticsProperty<Color>('hintColor', hintColor, defaultValue: defaultData.hintColor));
    properties.add(DiagnosticsProperty<Color>('errorColor', errorColor, defaultValue: defaultData.errorColor));
    properties.add(DiagnosticsProperty<Color>('toggleableActiveColor', toggleableActiveColor, defaultValue: defaultData.toggleableActiveColor));
    properties.add(DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme));
    properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme));
    properties.add(DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme));
    properties.add(DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme));
    properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme));
    properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme));
    properties.add(DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme));
    properties.add(DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
    properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
1099
    properties.add(DiagnosticsProperty<TabBarTheme>('tabBarTheme', tabBarTheme));
1100
    properties.add(DiagnosticsProperty<CardTheme>('cardTheme', cardTheme));
1101 1102
    properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
1103
    properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme));
1104
    properties.add(DiagnosticsProperty<AppBarTheme>('appBarTheme', appBarTheme, defaultValue: defaultData.appBarTheme));
1105
    properties.add(DiagnosticsProperty<BottomAppBarTheme>('bottomAppBarTheme', bottomAppBarTheme, defaultValue: defaultData.bottomAppBarTheme));
1106
    properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme));
1107
    properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme));
1108
    properties.add(DiagnosticsProperty<FloatingActionButtonThemeData>('floatingActionButtonThemeData', floatingActionButtonTheme, defaultValue: defaultData.floatingActionButtonTheme));
1109
    properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography));
xster's avatar
xster committed
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
    properties.add(DiagnosticsProperty<CupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme));
  }
}

/// A [CupertinoThemeData] that defers unspecified theme attributes to an
/// upstream Material [ThemeData].
///
/// This type of [CupertinoThemeData] is used by the Material [Theme] to
/// harmonize the [CupertinoTheme] with the material theme's colors and text
/// styles.
///
/// In the most basic case, [ThemeData]'s `cupertinoOverrideTheme` is null and
/// and descendant Cupertino widgets' styling is derived from the Material theme.
///
/// To override individual parts of the Material-derived Cupertino styling,
/// `cupertinoOverrideTheme`'s construction parameters can be used.
///
/// To completely decouple the Cupertino styling from Material theme derivation,
/// another [CupertinoTheme] widget can be inserted as a descendant of the
/// Material [Theme]. On a [MaterialApp], this can be done using the `builder`
/// parameter on the constructor.
///
/// See also:
///
///  * [CupertinoThemeData], whose null constructor parameters default to
///    reasonable iOS styling defaults rather than harmonizing with a Material
///    theme.
///  * [Theme], widget which inserts a [CupertinoTheme] with this
///    [MaterialBasedCupertinoThemeData].
// This class subclasses CupertinoThemeData rather than composes one because it
// _is_ a CupertinoThemeData with partially altered behavior. e.g. its textTheme
// is from the superclass and based on the primaryColor but the primaryColor
// comes from the Material theme unless overridden.
class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
  /// Create a [MaterialBasedCupertinoThemeData] based on a Material [ThemeData]
  /// and its `cupertinoOverrideTheme`.
  ///
  /// The [materialTheme] parameter must not be null.
  MaterialBasedCupertinoThemeData({
    @required ThemeData materialTheme,
  }) : assert(materialTheme != null),
       _materialTheme = materialTheme,
       // Pass all values to the superclass so Material-agnostic properties
       // like barBackgroundColor can still behave like a normal
       // CupertinoThemeData.
       super.raw(
         materialTheme.cupertinoOverrideTheme?.brightness,
         materialTheme.cupertinoOverrideTheme?.primaryColor,
         materialTheme.cupertinoOverrideTheme?.primaryContrastingColor,
         materialTheme.cupertinoOverrideTheme?.textTheme,
         materialTheme.cupertinoOverrideTheme?.barBackgroundColor,
         materialTheme.cupertinoOverrideTheme?.scaffoldBackgroundColor,
       );

  final ThemeData _materialTheme;

  @override
  Brightness get brightness => _materialTheme.cupertinoOverrideTheme?.brightness ?? _materialTheme.brightness;

  @override
  Color get primaryColor => _materialTheme.cupertinoOverrideTheme?.primaryColor ?? _materialTheme.colorScheme.primary;

  @override
  Color get primaryContrastingColor => _materialTheme.cupertinoOverrideTheme?.primaryContrastingColor ?? _materialTheme.colorScheme.onPrimary;

  @override
  Color get scaffoldBackgroundColor => _materialTheme.cupertinoOverrideTheme?.scaffoldBackgroundColor ?? _materialTheme.scaffoldBackgroundColor;

  /// Copies the [ThemeData]'s `cupertinoOverrideTheme`.
  ///
  /// Only the specified override attributes of the [ThemeData]'s
  /// `cupertinoOverrideTheme` and the newly specified parameters are in the
  /// returned [CupertinoThemeData]. No derived attributes from iOS defaults or
  /// from cascaded Material theme attributes are copied.
  ///
  /// [MaterialBasedCupertinoThemeData.copyWith] cannot change the base
  /// Material [ThemeData]. To change the base Material [ThemeData], create a
  /// new Material [Theme] and use `copyWith` on the Material [ThemeData]
  /// instead.
  @override
  CupertinoThemeData copyWith({
    Brightness brightness,
    Color primaryColor,
    Color primaryContrastingColor,
    CupertinoTextThemeData textTheme,
    Color barBackgroundColor,
    Color scaffoldBackgroundColor,
  }) {
    return _materialTheme.cupertinoOverrideTheme?.copyWith(
      brightness: brightness,
      primaryColor: primaryColor,
      primaryContrastingColor: primaryContrastingColor,
      textTheme: textTheme,
      barBackgroundColor: barBackgroundColor,
      scaffoldBackgroundColor: scaffoldBackgroundColor,
    ) ?? CupertinoThemeData(
      brightness: brightness,
      primaryColor: primaryColor,
      primaryContrastingColor: primaryContrastingColor,
      textTheme: textTheme,
      barBackgroundColor: barBackgroundColor,
      scaffoldBackgroundColor: scaffoldBackgroundColor,
    );
1213
  }
1214
}
1215

1216 1217
class _IdentityThemeDataCacheKey {
  _IdentityThemeDataCacheKey(this.baseTheme, this.localTextGeometry);
1218

1219
  final ThemeData baseTheme;
1220 1221
  final TextTheme localTextGeometry;

1222 1223
  // Using XOR to make the hash function as fast as possible (e.g. Jenkins is
  // noticeably slower).
1224
  @override
1225
  int get hashCode => identityHashCode(baseTheme) ^ identityHashCode(localTextGeometry);
1226 1227

  @override
1228 1229 1230 1231
  bool operator ==(Object other) {
    // We are explicitly ignoring the possibility that the types might not
    // match in the interests of speed.
    final _IdentityThemeDataCacheKey otherKey = other;
1232
    return identical(baseTheme, otherKey.baseTheme) && identical(localTextGeometry, otherKey.localTextGeometry);
1233 1234 1235
  }
}

1236 1237 1238 1239 1240 1241
/// Cache of objects of limited size that uses the first in first out eviction
/// strategy (a.k.a least recently inserted).
///
/// The key that was inserted before all other keys is evicted first, i.e. the
/// one inserted least recently.
class _FifoCache<K, V> {
1242
  _FifoCache(this._maximumSize) : assert(_maximumSize != null && _maximumSize > 0);
1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268

  /// In Dart the map literal uses a linked hash-map implementation, whose keys
  /// are stored such that [Map.keys] returns them in the order they were
  /// inserted.
  final Map<K, V> _cache = <K, V>{};

  /// Maximum number of entries to store in the cache.
  ///
  /// Once this many entries have been cached, the entry inserted least recently
  /// is evicted when adding a new entry.
  final int _maximumSize;

  /// Returns the previously cached value for the given key, if available;
  /// if not, calls the given callback to obtain it first.
  ///
  /// The arguments must not be null.
  V putIfAbsent(K key, V loader()) {
    assert(key != null);
    assert(loader != null);
    final V result = _cache[key];
    if (result != null)
      return result;
    if (_cache.length == _maximumSize)
      _cache.remove(_cache.keys.first);
    return _cache[key] = loader();
  }
1269
}