blob: 24fcbd34b131d70242f3f2881ecd4633d968e1d3 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
/// A utility class for dealing with the overlay color needed
/// to indicate elevation of surfaces.
abstract final class ElevationOverlay {
/// 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.
///
/// 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.
static Color applySurfaceTint(Color color, Color? surfaceTint, double elevation) {
if (surfaceTint != null && surfaceTint != Colors.transparent) {
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);
}
/// Applies an overlay color to a surface color to indicate
/// the level of its elevation in a dark theme.
///
/// 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.
///
/// 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].
///
/// Otherwise it will just return the [color] unmodified.
///
/// See also:
///
/// * [ThemeData.applyElevationOverlayColor] which controls the whether
/// an overlay color will be applied to indicate elevation.
/// * [overlayColor] which computes the needed overlay color.
/// * [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.
static Color applyOverlay(BuildContext context, Color color, double elevation) {
final ThemeData theme = Theme.of(context);
if (elevation > 0.0 &&
theme.applyElevationOverlayColor &&
theme.brightness == Brightness.dark &&
color.withOpacity(1.0) == theme.colorScheme.surface.withOpacity(1.0)) {
return colorWithOverlay(color, theme.colorScheme.onSurface, elevation);
}
return color;
}
/// Computes the appropriate overlay color used to indicate elevation in
/// dark themes.
///
/// 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.
///
/// See also:
///
/// * https://material.io/design/color/dark-theme.html#properties which
/// specifies the exact overlay values for a given elevation.
static Color overlayColor(BuildContext context, double elevation) {
final ThemeData theme = Theme.of(context);
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).
///
/// 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.
///
/// 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) {
// 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;
return color.withOpacity(opacity);
}
}
// 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;
}
// BEGIN GENERATED TOKEN PROPERTIES - SurfaceTint
// 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.
// 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
];
// END GENERATED TOKEN PROPERTIES - SurfaceTint