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

5 6 7 8
import 'dart:math' as math;

import 'package:flutter/widgets.dart';

9
import 'colors.dart';
10 11
import 'theme.dart';

12
/// A utility class for dealing with the overlay color needed
13
/// to indicate elevation of surfaces.
14
abstract final class ElevationOverlay {
15 16 17 18 19 20 21 22
  /// Applies a surface tint color to a given container color to indicate
  /// the level of its elevation.
  ///
  /// With Material Design 3, some components will use a "surface tint" color
  /// overlay with an opacity applied to their base color to indicate they are
  /// elevated. The amount of opacity will vary with the elevation as described
  /// in: https://m3.material.io/styles/color/the-color-system/color-roles.
  ///
23 24 25 26
  /// If [surfaceTint] is not null and not completely transparent ([Color.alpha]
  /// is 0), then the returned color will be the given [color] with the
  /// [surfaceTint] of the appropriate opacity applied to it. Otherwise it will
  /// just return [color] unmodified.
27
  static Color applySurfaceTint(Color color, Color? surfaceTint, double elevation) {
28
    if (surfaceTint != null && surfaceTint != Colors.transparent) {
29 30 31 32 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
      return Color.alphaBlend(surfaceTint.withOpacity(_surfaceTintOpacityForElevation(elevation)), color);
    }
    return color;
  }

  // Calculates the opacity of the surface tint color from the elevation by
  // looking it up in the token generated table of opacities, interpolating
  // between values as needed. If the elevation is outside the range of values
  // in the table it will clamp to the smallest or largest opacity.
  static double _surfaceTintOpacityForElevation(double elevation) {
    if (elevation < _surfaceTintElevationOpacities[0].elevation) {
      // Elevation less than the first entry, so just clamp it to the first one.
      return _surfaceTintElevationOpacities[0].opacity;
    }

    // Walk the opacity list and find the closest match(es) for the elevation.
    int index = 0;
    while (elevation >= _surfaceTintElevationOpacities[index].elevation) {
      // If we found it exactly or walked off the end of the list just return it.
      if (elevation == _surfaceTintElevationOpacities[index].elevation ||
          index + 1 == _surfaceTintElevationOpacities.length) {
        return _surfaceTintElevationOpacities[index].opacity;
      }
      index += 1;
    }

    // Interpolate between the two opacity values
    final _ElevationOpacity lower = _surfaceTintElevationOpacities[index - 1];
    final _ElevationOpacity upper = _surfaceTintElevationOpacities[index];
    final double t = (elevation - lower.elevation) / (upper.elevation - lower.elevation);
    return lower.opacity + t * (upper.opacity - lower.opacity);
  }

62 63
  /// Applies an overlay color to a surface color to indicate
  /// the level of its elevation in a dark theme.
64
  ///
65 66 67 68
  /// If using Material Design 3, this type of color overlay is no longer used.
  /// Instead a "surface tint" overlay is used instead. See [applySurfaceTint],
  /// [ThemeData.useMaterial3] for more information.
  ///
69 70 71 72 73 74 75 76 77 78 79 80
  /// Material drop shadows can be difficult to see in a dark theme, so the
  /// elevation of a surface should be portrayed with an "overlay" in addition
  /// to the shadow. As the elevation of the component increases, the
  /// overlay increases in opacity. This function computes and applies this
  /// overlay to a given color as needed.
  ///
  /// If the ambient theme is dark ([ThemeData.brightness] is [Brightness.dark]),
  /// and [ThemeData.applyElevationOverlayColor] is true, and the given
  /// [color] is [ColorScheme.surface] then this will return a version of
  /// the [color] with a semi-transparent [ColorScheme.onSurface] overlaid
  /// on top of it. The opacity of the overlay is computed based on the
  /// [elevation].
81 82 83 84 85
  ///
  /// Otherwise it will just return the [color] unmodified.
  ///
  /// See also:
  ///
86 87 88
  ///  * [ThemeData.applyElevationOverlayColor] which controls the whether
  ///    an overlay color will be applied to indicate elevation.
  ///  * [overlayColor] which computes the needed overlay color.
89 90 91
  ///  * [Material] which uses this to apply an elevation overlay to its surface.
  ///  * <https://material.io/design/color/dark-theme.html>, which specifies how
  ///    the overlay should be applied.
92
  static Color applyOverlay(BuildContext context, Color color, double elevation) {
93
    final ThemeData theme = Theme.of(context);
94 95
    if (elevation > 0.0 &&
        theme.applyElevationOverlayColor &&
96
        theme.brightness == Brightness.dark &&
97
        color.withOpacity(1.0) == theme.colorScheme.surface.withOpacity(1.0)) {
98
      return colorWithOverlay(color, theme.colorScheme.onSurface, elevation);
99 100 101 102 103 104 105
    }
    return color;
  }

  /// Computes the appropriate overlay color used to indicate elevation in
  /// dark themes.
  ///
106 107 108 109
  /// If using Material Design 3, this type of color overlay is no longer used.
  /// Instead a "surface tint" overlay is used instead. See [applySurfaceTint],
  /// [ThemeData.useMaterial3] for more information.
  ///
110 111
  /// See also:
  ///
112 113
  ///  * https://material.io/design/color/dark-theme.html#properties which
  ///    specifies the exact overlay values for a given elevation.
114
  static Color overlayColor(BuildContext context, double elevation) {
115
    final ThemeData theme = Theme.of(context);
116 117 118 119 120 121
    return _overlayColor(theme.colorScheme.onSurface, elevation);
  }

  /// Returns a color blended by laying a semi-transparent overlay (using the
  /// [overlay] color) on top of a surface (using the [surface] color).
  ///
122 123 124 125
  /// If using Material Design 3, this type of color overlay is no longer used.
  /// Instead a "surface tint" overlay is used instead. See [applySurfaceTint],
  /// [ThemeData.useMaterial3] for more information.
  ///
126 127 128 129 130 131 132 133 134 135
  /// The opacity of the overlay depends on [elevation]. As [elevation]
  /// increases, the opacity will also increase.
  ///
  /// See https://material.io/design/color/dark-theme.html#properties.
  static Color colorWithOverlay(Color surface, Color overlay, double elevation) {
    return Color.alphaBlend(_overlayColor(overlay, elevation), surface);
  }

  /// Applies an opacity to [color] based on [elevation].
  static Color _overlayColor(Color color, double elevation) {
136 137 138 139
    // Compute the opacity for the given elevation
    // This formula matches the values in the spec:
    // https://material.io/design/color/dark-theme.html#properties
    final double opacity = (4.5 * math.log(elevation + 1) + 2) / 100.0;
140
    return color.withOpacity(opacity);
141 142
  }
}
143 144 145 146 147 148 149 150 151

// A data class to hold the opacity at a given elevation.
class _ElevationOpacity {
  const _ElevationOpacity(this.elevation, this.opacity);

  final double elevation;
  final double opacity;
}

152
// BEGIN GENERATED TOKEN PROPERTIES - SurfaceTint
153

154 155 156 157
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.
158 159 160 161 162 163 164 165 166 167 168 169 170 171

// Surface tint opacities based on elevations according to the
// Material Design 3 specification:
//   https://m3.material.io/styles/color/the-color-system/color-roles
// Ordered by increasing elevation.
const List<_ElevationOpacity> _surfaceTintElevationOpacities = <_ElevationOpacity>[
  _ElevationOpacity(0.0, 0.0),   // Elevation level 0
  _ElevationOpacity(1.0, 0.05),  // Elevation level 1
  _ElevationOpacity(3.0, 0.08),  // Elevation level 2
  _ElevationOpacity(6.0, 0.11),  // Elevation level 3
  _ElevationOpacity(8.0, 0.12),  // Elevation level 4
  _ElevationOpacity(12.0, 0.14), // Elevation level 5
];

172
// END GENERATED TOKEN PROPERTIES - SurfaceTint