Unverified Commit 6ec0b835 authored by Darren Austin's avatar Darren Austin Committed by GitHub

Add support for surface tint color overlays to `Material` widget. (#100036)

parent e99a66a4
......@@ -21,6 +21,7 @@ import 'package:gen_defaults/dialog_template.dart';
import 'package:gen_defaults/fab_template.dart';
import 'package:gen_defaults/navigation_bar_template.dart';
import 'package:gen_defaults/navigation_rail_template.dart';
import 'package:gen_defaults/surface_tint.dart';
import 'package:gen_defaults/typography_template.dart';
Map<String, dynamic> _readTokenFile(String fileName) {
......@@ -70,9 +71,10 @@ Future<void> main(List<String> args) async {
tokens['colorsLight'] = _readTokenFile('color_light.json');
tokens['colorsDark'] = _readTokenFile('color_dark.json');
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
FABTemplate('$materialLib/floating_action_button.dart', tokens).updateFile();
NavigationBarTemplate('$materialLib/navigation_bar.dart', tokens).updateFile();
NavigationRailTemplate('$materialLib/navigation_rail.dart', tokens).updateFile();
SurfaceTintTemplate('$materialLib/elevation_overlay.dart', tokens).updateFile();
TypographyTemplate('$materialLib/typography.dart', tokens).updateFile();
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
}
// 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 'template.dart';
class SurfaceTintTemplate extends TokenTemplate {
const SurfaceTintTemplate(String fileName, Map<String, dynamic> tokens) : super(fileName, tokens);
@override
String generate() => '''
// Generated version ${tokens["version"]}
// 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(${tokens['md.sys.elevation.level0']}, 0.0), // Elevation level 0
_ElevationOpacity(${tokens['md.sys.elevation.level1']}, 0.05), // Elevation level 1
_ElevationOpacity(${tokens['md.sys.elevation.level2']}, 0.08), // Elevation level 2
_ElevationOpacity(${tokens['md.sys.elevation.level3']}, 0.11), // Elevation level 3
_ElevationOpacity(${tokens['md.sys.elevation.level4']}, 0.12), // Elevation level 4
_ElevationOpacity(${tokens['md.sys.elevation.level5']}, 0.14), // Elevation level 5
];
''';
}
......@@ -381,6 +381,8 @@ class _RawMaterialButtonState extends State<RawMaterialButton> with MaterialStat
textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
shape: effectiveShape,
color: widget.fillColor,
// For compatibility during the M3 migration the default shadow needs to be passed.
shadowColor: Theme.of(context).useMaterial3 ? Theme.of(context).shadowColor : null,
type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
animationDuration: widget.animationDuration,
clipBehavior: widget.clipBehavior,
......
......@@ -9,15 +9,65 @@ import 'package:flutter/widgets.dart';
import 'theme.dart';
/// A utility class for dealing with the overlay color needed
/// to indicate elevation of surfaces in a dark theme.
/// to indicate elevation of surfaces.
class ElevationOverlay {
// This class is not meant to be instantiated or extended; this constructor
// prevents instantiation and extension.
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 then the returned color will be the given
/// [color] with the [surfaceTint] of the appropriate opacity applies to it.
/// Otherwise it will just return [color] unmodified.
static Color applySurfaceTint(Color color, Color? surfaceTint, double elevation) {
if (surfaceTint != null) {
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
......@@ -55,6 +105,10 @@ class ElevationOverlay {
/// 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
......@@ -67,6 +121,10 @@ class ElevationOverlay {
/// 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.
///
......@@ -84,3 +142,34 @@ class ElevationOverlay {
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
// Generated code to the end of this file. Do not edit by hand.
// These defaults are generated from the Material Design Token
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
// Generated version v0_90
// 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
......@@ -111,10 +111,11 @@ abstract class MaterialInkController {
///
/// In general, the features of a [Material] should not change over time (e.g. a
/// [Material] should not change its [color], [shadowColor] or [type]).
/// Changes to [elevation] and [shadowColor] are animated for [animationDuration].
/// Changes to [shape] are animated if [type] is not [MaterialType.transparency]
/// and [ShapeBorder.lerp] between the previous and next [shape] values is
/// supported. Shape changes are also animated for [animationDuration].
/// Changes to [elevation], [shadowColor] and [surfaceTintColor] are animated
/// for [animationDuration]. Changes to [shape] are animated if [type] is
/// not [MaterialType.transparency] and [ShapeBorder.lerp] between the previous
/// and next [shape] values is supported. Shape changes are also animated
/// for [animationDuration].
///
/// ## Shape
///
......@@ -166,7 +167,7 @@ abstract class MaterialInkController {
class Material extends StatefulWidget {
/// Creates a piece of material.
///
/// The [type], [elevation], [shadowColor], [borderOnForeground],
/// The [type], [elevation], [borderOnForeground],
/// [clipBehavior], and [animationDuration] arguments must not be null.
/// Additionally, [elevation] must be non-negative.
///
......@@ -181,6 +182,7 @@ class Material extends StatefulWidget {
this.elevation = 0.0,
this.color,
this.shadowColor,
this.surfaceTintColor,
this.textStyle,
this.borderRadius,
this.shape,
......@@ -217,16 +219,20 @@ class Material extends StatefulWidget {
/// widget conceptually defines an independent printed piece of material.
///
/// Defaults to 0. Changing this value will cause the shadow and the elevation
/// overlay to animate over [Material.animationDuration].
/// overlay or surface tint to animate over [Material.animationDuration].
///
/// The value is non-negative.
///
/// See also:
///
/// * [ThemeData.useMaterial3] which defines whether a surface tint or
/// elevation overlay is used to indicate elevation.
/// * [ThemeData.applyElevationOverlayColor] which controls the whether
/// an overlay color will be applied to indicate elevation.
/// * [Material.color] which may have an elevation overlay applied.
///
/// * [Material.shadowColor] which will be used for the color of a drop shadow.
/// * [Material.surfaceTintColor] which will be used as the overlay tint to
/// show elevation.
/// {@endtemplate}
final double elevation;
......@@ -235,29 +241,59 @@ class Material extends StatefulWidget {
/// Must be opaque. To create a transparent piece of material, use
/// [MaterialType.transparency].
///
/// To support dark themes, if the surrounding
/// [ThemeData.applyElevationOverlayColor] is true and [ThemeData.brightness]
/// is [Brightness.dark] then a semi-transparent overlay color will be
/// composited on top of this color to indicate the elevation.
/// If [ThemeData.useMaterial3] is true then an optional [surfaceTintColor]
/// overlay may be applied on top of this color to indicate elevation.
///
/// If [ThemeData.useMaterial3] is false and [ThemeData.applyElevationOverlayColor]
/// is true and [ThemeData.brightness] is [Brightness.dark] then a
/// semi-transparent overlay color will be composited on top of this
/// color to indicate the elevation. This is no longer needed for Material
/// Design 3, which uses [surfaceTintColor].
///
/// By default, the color is derived from the [type] of material.
final Color? color;
/// The color to paint the shadow below the material.
///
/// If null, [ThemeData.shadowColor] is used, which defaults to fully opaque black.
/// When [ThemeData.useMaterial3] is true, and this is null, then no drop
/// shadow will be rendered for this material. If it is non-null, then this
/// color will be used to render a drop shadow below the material.
///
/// 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.
/// When [ThemeData.useMaterial3] is false, and this is null, then
/// [ThemeData.shadowColor] is used, which defaults to fully opaque black.
///
/// See also:
///
/// * [ThemeData.useMaterial3], which determines the default value for this
/// property if it is null.
/// * [ThemeData.applyElevationOverlayColor], which turns elevation overlay
/// on or off for dark themes.
final Color? shadowColor;
/// The color of the surface tint overlay applied to the material color
/// to indicate elevation.
///
/// Material Design 3 introduced a new way for some components to indicate
/// their elevation by using a surface tint color overlay on top of the
/// base material [color]. This overlay is painted with an opacity that is
/// related to the [elevation] of the material.
///
/// If [ThemeData.useMaterial3] is false, then this property is not used.
///
/// If [ThemeData.useMaterial3] is true and [surfaceTintColor] is not null,
/// then it will be used to overlay the base [color] with an opacity based
/// on the [elevation].
///
/// Otherwise, no surface tint will be applied.
///
/// See also:
///
/// * [ThemeData.useMaterial3], which turns this feature on.
/// * [ElevationOverlay.applySurfaceTint], which is used to implement the
/// tint.
/// * https://m3.material.io/styles/color/the-color-system/color-roles
/// which specifies how the overlay is applied.
final Color? surfaceTintColor;
/// The typographical style to use for text within this material.
final TextStyle? textStyle;
......@@ -287,7 +323,7 @@ class Material extends StatefulWidget {
final Clip clipBehavior;
/// Defines the duration of animated changes for [shape], [elevation],
/// [shadowColor] and the elevation overlay if it is applied.
/// [shadowColor], [surfaceTintColor] and the elevation overlay if it is applied.
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
......@@ -327,6 +363,7 @@ class Material extends StatefulWidget {
properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
textStyle?.debugFillProperties(properties, prefix: 'textStyle.');
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('borderOnForeground', borderOnForeground, defaultValue: true));
......@@ -362,6 +399,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final Color? backgroundColor = _getBackgroundColor(context);
assert(
backgroundColor != null || widget.type == MaterialType.transparency,
......@@ -403,14 +441,18 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
// we choose not to as we want the change from the fast-path to the
// slow-path to be noticeable in the construction site of Material.
if (widget.type == MaterialType.canvas && widget.shape == null && widget.borderRadius == null) {
final Color color = Theme.of(context).useMaterial3
? ElevationOverlay.applySurfaceTint(backgroundColor!, widget.surfaceTintColor, widget.elevation)
: ElevationOverlay.applyOverlay(context, backgroundColor!, widget.elevation);
return AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn,
duration: widget.animationDuration,
shape: BoxShape.rectangle,
clipBehavior: widget.clipBehavior,
elevation: widget.elevation,
color: ElevationOverlay.applyOverlay(context, backgroundColor!, widget.elevation),
shadowColor: widget.shadowColor ?? Theme.of(context).shadowColor,
color: color,
shadowColor: widget.shadowColor ?? (theme.useMaterial3 ? const Color(0x00000000) : theme.shadowColor),
animateColor: false,
child: contents,
);
......@@ -435,7 +477,8 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
clipBehavior: widget.clipBehavior,
elevation: widget.elevation,
color: backgroundColor!,
shadowColor: widget.shadowColor ?? Theme.of(context).shadowColor,
shadowColor: widget.shadowColor ?? (theme.useMaterial3 ? const Color(0x00000000) : theme.shadowColor),
surfaceTintColor: widget.surfaceTintColor,
child: contents,
);
}
......@@ -694,6 +737,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
required this.elevation,
required this.color,
required this.shadowColor,
required this.surfaceTintColor,
Curve curve = Curves.linear,
required Duration duration,
}) : assert(child != null),
......@@ -738,6 +782,9 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
/// The target shadow color.
final Color shadowColor;
/// The target surface tint color.
final Color? surfaceTintColor;
@override
_MaterialInteriorState createState() => _MaterialInteriorState();
......@@ -753,6 +800,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior> {
Tween<double>? _elevation;
ColorTween? _surfaceTintColor;
ColorTween? _shadowColor;
ShapeBorderTween? _border;
......@@ -768,6 +816,13 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
widget.shadowColor,
(dynamic value) => ColorTween(begin: value as Color),
) as ColorTween?;
_surfaceTintColor = widget.surfaceTintColor != null
? visitor(
_surfaceTintColor,
widget.surfaceTintColor,
(dynamic value) => ColorTween(begin: value as Color),
) as ColorTween?
: null;
_border = visitor(
_border,
widget.shape,
......@@ -779,6 +834,9 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
Widget build(BuildContext context) {
final ShapeBorder shape = _border!.evaluate(animation)!;
final double elevation = _elevation!.evaluate(animation);
final Color color = Theme.of(context).useMaterial3
? ElevationOverlay.applySurfaceTint(widget.color, _surfaceTintColor?.evaluate(animation), elevation)
: ElevationOverlay.applyOverlay(context, widget.color, elevation);
return PhysicalShape(
clipper: ShapeBorderClipper(
shape: shape,
......@@ -786,7 +844,7 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
),
clipBehavior: widget.clipBehavior,
elevation: elevation,
color: ElevationOverlay.applyOverlay(context, widget.color, elevation),
color: color,
shadowColor: _shadowColor!.evaluate(animation)!,
child: _ShapeBorderPaint(
shape: shape,
......
......@@ -437,7 +437,7 @@ class ThemeData with Diagnosticable {
dialogBackgroundColor ??= colorScheme.background;
indicatorColor ??= onPrimarySurfaceColor;
errorColor ??= colorScheme.error;
applyElevationOverlayColor ??= isDark;
applyElevationOverlayColor ??= brightness == Brightness.dark;
}
applyElevationOverlayColor ??= false;
primarySwatch ??= Colors.blue;
......@@ -922,6 +922,7 @@ class ThemeData with Diagnosticable {
factory ThemeData.from({
required ColorScheme colorScheme,
TextTheme? textTheme,
bool? useMaterial3,
}) {
final bool isDark = colorScheme.brightness == Brightness.dark;
......@@ -947,6 +948,7 @@ class ThemeData with Diagnosticable {
errorColor: colorScheme.error,
textTheme: textTheme,
applyElevationOverlayColor: isDark,
useMaterial3: useMaterial3,
);
}
......@@ -1003,6 +1005,11 @@ class ThemeData with Diagnosticable {
/// Apply a semi-transparent overlay color on Material surfaces to indicate
/// elevation for dark themes.
///
/// If [useMaterial3] is true, then this flag is ignored as there is a new
/// [Material.surfaceTintColor] used to create an overlay for Material 3.
/// This flag is meant only for the Material 2 elevation overlay for dark
/// themes.
///
/// 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
......@@ -1161,6 +1168,7 @@ class ThemeData with Diagnosticable {
/// * [AlertDialog]
/// * [Dialog]
/// * [FloatingActionButton]
/// * [Material]
/// * [NavigationBar]
/// * [NavigationRail]
///
......
// 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 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('applySurfaceTint with null surface tint returns given color', () {
final Color result = ElevationOverlay.applySurfaceTint(const Color(0xff888888), null, 42.0);
expect(result, equals(const Color(0xFF888888)));
});
test('applySurfaceTint with exact elevation levels uses the right opacity overlay', () {
const Color baseColor = Color(0xff888888);
const Color surfaceTintColor = Color(0xff44CCFF);
Color overlayWithOpacity(double opacity) {
return Color.alphaBlend(surfaceTintColor.withOpacity(opacity), baseColor);
}
// Based on values from the spec:
// https://m3.material.io/styles/color/the-color-system/color-roles
// Elevation level 0 (0.0) - should have opacity 0.0.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 0.0), equals(overlayWithOpacity(0.0)));
// Elevation level 1 (1.0) - should have opacity 0.05.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 1.0), equals(overlayWithOpacity(0.05)));
// Elevation level 2 (3.0) - should have opacity 0.08.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 3.0), equals(overlayWithOpacity(0.08)));
// Elevation level 3 (6.0) - should have opacity 0.11`.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 6.0), equals(overlayWithOpacity(0.11)));
// Elevation level 4 (8.0) - should have opacity 0.12.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 8.0), equals(overlayWithOpacity(0.12)));
// Elevation level 5 (12.0) - should have opacity 0.14.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 12.0), equals(overlayWithOpacity(0.14)));
});
test('applySurfaceTint with elevation lower than level 0 should have no overlay', () {
const Color baseColor = Color(0xff888888);
const Color surfaceTintColor = Color(0xff44CCFF);
Color overlayWithOpacity(double opacity) {
return Color.alphaBlend(surfaceTintColor.withOpacity(opacity), baseColor);
}
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, -42.0), equals(overlayWithOpacity(0.0)));
});
test('applySurfaceTint with elevation higher than level 5 should have no level 5 overlay', () {
const Color baseColor = Color(0xff888888);
const Color surfaceTintColor = Color(0xff44CCFF);
Color overlayWithOpacity(double opacity) {
return Color.alphaBlend(surfaceTintColor.withOpacity(opacity), baseColor);
}
// Elevation level 5 (12.0) - should have opacity 0.14.
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 42.0), equals(overlayWithOpacity(0.14)));
});
test('applySurfaceTint with elevation between two levels should interpolate the opacity', () {
const Color baseColor = Color(0xff888888);
const Color surfaceTintColor = Color(0xff44CCFF);
Color overlayWithOpacity(double opacity) {
return Color.alphaBlend(surfaceTintColor.withOpacity(opacity), baseColor);
}
// Elevation between level 4 (8.0) and level 5 (12.0) should be interpolated
// between the opacities 0.12 and 0.14.
// One third (0.3): (elevation 9.2) -> (opacity 0.126)
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 9.2), equals(overlayWithOpacity(0.126)));
// Half way (0.5): (elevation 10.0) -> (opacity 0.13)
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 10.0), equals(overlayWithOpacity(0.13)));
// Two thirds (0.6): (elevation 10.4) -> (opacity 0.132)
expect(ElevationOverlay.applySurfaceTint(baseColor, surfaceTintColor, 10.4), equals(overlayWithOpacity(0.132)));
});
}
......@@ -25,6 +25,7 @@ class NotifyMaterial extends StatelessWidget {
Widget buildMaterial({
double elevation = 0.0,
Color shadowColor = const Color(0xFF00FF00),
Color? surfaceTintColor,
Color color = const Color(0xFF0000FF),
}) {
return Center(
......@@ -34,6 +35,7 @@ Widget buildMaterial({
child: Material(
color: color,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
elevation: elevation,
shape: const CircleBorder(),
),
......@@ -99,6 +101,7 @@ void main() {
const Material(
color: Color(0xFFFFFFFF),
shadowColor: Color(0xffff0000),
surfaceTintColor: Color(0xff0000ff),
textStyle: TextStyle(color: Color(0xff00ff00)),
borderRadius: BorderRadiusDirectional.all(Radius.circular(10)),
).debugFillProperties(builder);
......@@ -112,6 +115,7 @@ void main() {
'type: canvas',
'color: Color(0xffffffff)',
'shadowColor: Color(0xffff0000)',
'surfaceTintColor: Color(0xff0000ff)',
'textStyle.inherit: true',
'textStyle.color: Color(0xff00ff00)',
'borderRadius: BorderRadiusDirectional.circular(10.0)',
......@@ -265,12 +269,72 @@ void main() {
expect(pressed, isTrue);
});
group('Elevation Overlay', () {
group('Surface Tint Overlay', () {
testWidgets('applyElevationOverlayColor does not effect anything with useMaterial3 set to true', (WidgetTester tester) async {
const Color surfaceColor = Color(0xFF121212);
await tester.pumpWidget(Theme(
data: ThemeData(
useMaterial3: true,
applyElevationOverlayColor: true,
colorScheme: const ColorScheme.dark().copyWith(surface: surfaceColor),
),
child: buildMaterial(color: surfaceColor, elevation: 8.0),
));
final RenderPhysicalShape model = getModel(tester);
expect(model.color, equals(surfaceColor));
});
testWidgets('surfaceTintColor is used to as an overlay to indicate elevation', (WidgetTester tester) async {
const Color baseColor = Color(0xFF121212);
const Color surfaceTintColor = Color(0xff44CCFF);
// With no surfaceTintColor specified, it should not apply an overlay
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: true,
),
child: buildMaterial(
color: baseColor,
elevation: 12.0,
),
),
);
await tester.pumpAndSettle();
final RenderPhysicalShape noTintModel = getModel(tester);
expect(noTintModel.color, equals(baseColor));
// With surfaceTintColor specified, it should not apply an overlay based
// on the elevation.
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: true,
),
child: buildMaterial(
color: baseColor,
surfaceTintColor: surfaceTintColor,
elevation: 12.0,
),
),
);
await tester.pumpAndSettle();
final RenderPhysicalShape tintModel = getModel(tester);
// Final color should be the base with a tint of 0.14 opacity or 0xff192c33
expect(tintModel.color, equals(const Color(0xff192c33)));
});
}); // Surface Tint Overlay group
group('Elevation Overlay M2', () {
// These tests only apply to the Material 2 overlay mechanism. This group
// can be removed after migration to Material 3 is complete.
testWidgets('applyElevationOverlayColor set to false does not change surface color', (WidgetTester tester) async {
const Color surfaceColor = Color(0xFF121212);
await tester.pumpWidget(Theme(
data: ThemeData(
useMaterial3: false,
applyElevationOverlayColor: false,
colorScheme: const ColorScheme.dark().copyWith(surface: surfaceColor),
),
......@@ -303,6 +367,7 @@ void main() {
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: false,
applyElevationOverlayColor: true,
colorScheme: const ColorScheme.dark().copyWith(
surface: surfaceColor,
......@@ -325,6 +390,7 @@ void main() {
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: false,
applyElevationOverlayColor: true,
colorScheme: const ColorScheme.dark(),
),
......@@ -343,6 +409,7 @@ void main() {
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: false,
applyElevationOverlayColor: true,
colorScheme: const ColorScheme.light(),
),
......@@ -364,6 +431,7 @@ void main() {
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: false,
applyElevationOverlayColor: true,
colorScheme: const ColorScheme.dark(),
),
......@@ -390,6 +458,7 @@ void main() {
await tester.pumpWidget(
Theme(
data: ThemeData(
useMaterial3: false,
applyElevationOverlayColor: true,
colorScheme: const ColorScheme.dark(
surface: surfaceColor,
......@@ -407,7 +476,8 @@ void main() {
expect(model.color, equals(surfaceColorWithOverlay));
expect(model.color, isNot(equals(surfaceColor)));
});
});
}); // Elevation Overlay M2 group
group('Transparency clipping', () {
testWidgets('No clip by default', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment