Unverified Commit 10ae8698 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added MaterialStateProperty overlayColor to InkWell (#58650)

parent bbe18f75
......@@ -306,6 +306,7 @@ class InkResponse extends StatelessWidget {
this.focusColor,
this.hoverColor,
this.highlightColor,
this.overlayColor,
this.splashColor,
this.splashFactory,
this.enableFeedback = true,
......@@ -479,6 +480,31 @@ class InkResponse extends StatelessWidget {
/// * [splashFactory], which defines the appearance of the splash.
final Color highlightColor;
/// Defines the ink response focus, hover, and splash colors.
///
/// This default null property can be used as an alternative to
/// [focusColor], [hoverColor], and [splashColor]. If non-null,
/// it is resolved against one of [MaterialState.focused],
/// [MaterialState.hovered], and [MaterialState.pressed]. It's
/// convenient to use when the parent widget can pass along its own
/// MaterialStateProperty value for the overlay color.
///
/// [MaterialState.pressed] triggers a ripple (an ink splash), per
/// the current Material Design spec. The [overlayColor] doesn't map
/// a state to [highlightColor] because a separate highlight is not
/// used by the current design guidelines. See
/// https://material.io/design/interaction/states.html#pressed
///
/// If the overlay color is null or resolves to null, then [focusColor],
/// [hoverColor], [splashColor] and their defaults are used instead.
///
/// See also:
///
/// * The Material Design specification for overlay colors and how they
/// to a component's state:
/// <https://material.io/design/interaction/states.html#anatomy>.
final MaterialStateProperty<Color> overlayColor;
/// The splash color of the ink response. If this property is null then the
/// splash color of the theme, [ThemeData.splashColor], will be used.
///
......@@ -571,6 +597,7 @@ class InkResponse extends StatelessWidget {
focusColor: focusColor,
hoverColor: hoverColor,
highlightColor: highlightColor,
overlayColor: overlayColor,
splashColor: splashColor,
splashFactory: splashFactory,
enableFeedback: enableFeedback,
......@@ -619,6 +646,7 @@ class _InkResponseStateWidget extends StatefulWidget {
this.focusColor,
this.hoverColor,
this.highlightColor,
this.overlayColor,
this.splashColor,
this.splashFactory,
this.enableFeedback = true,
......@@ -654,6 +682,7 @@ class _InkResponseStateWidget extends StatefulWidget {
final Color focusColor;
final Color hoverColor;
final Color highlightColor;
final MaterialStateProperty<Color> overlayColor;
final Color splashColor;
final InteractiveInkFeatureFactory splashFactory;
final bool enableFeedback;
......@@ -760,13 +789,19 @@ class _InkResponseState extends State<_InkResponseStateWidget>
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes.isNotEmpty);
Color getHighlightColorForType(_HighlightType type) {
const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};
switch (type) {
// The pressed state triggers a ripple (ink splash), per the current
// Material Design spec. A separate highlight is no longer used.
// See https://material.io/design/interaction/states.html#pressed
case _HighlightType.pressed:
return widget.highlightColor ?? Theme.of(context).highlightColor;
case _HighlightType.focus:
return widget.focusColor ?? Theme.of(context).focusColor;
return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? Theme.of(context).focusColor;
case _HighlightType.hover:
return widget.hoverColor ?? Theme.of(context).hoverColor;
return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? Theme.of(context).hoverColor;
}
assert(false, 'Unhandled $_HighlightType $type');
return null;
......@@ -839,7 +874,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
final MaterialInkController inkController = Material.of(context);
final RenderBox referenceBox = context.findRenderObject() as RenderBox;
final Offset position = referenceBox.globalToLocal(globalPosition);
final Color color = widget.splashColor ?? Theme.of(context).splashColor;
const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
final Color color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null;
final BorderRadius borderRadius = widget.borderRadius;
final ShapeBorder customBorder = widget.customBorder;
......@@ -1180,6 +1216,7 @@ class InkWell extends InkResponse {
Color focusColor,
Color hoverColor,
Color highlightColor,
MaterialStateProperty<Color> overlayColor,
Color splashColor,
InteractiveInkFeatureFactory splashFactory,
double radius,
......@@ -1207,6 +1244,7 @@ class InkWell extends InkResponse {
focusColor: focusColor,
hoverColor: hoverColor,
highlightColor: highlightColor,
overlayColor: overlayColor,
splashColor: splashColor,
splashFactory: splashFactory,
radius: radius,
......
......@@ -120,6 +120,43 @@ void main() {
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
});
testWidgets('ink well changes color on hover with overlayColor', (WidgetTester tester) async {
// Same test as 'ink well changes color on hover' except that the
// hover color is specified with the overlayColor parameter.
await tester.pumpWidget(Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Container(
width: 100,
height: 100,
child: InkWell(
overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return const Color(0xff00ff00);
if (states.contains(MaterialState.focused))
return const Color(0xff0000ff);
if (states.contains(MaterialState.pressed))
return const Color(0xf00fffff);
return const Color(0xffbadbad); // Shouldn't happen.
}),
onTap: () { },
onLongPress: () { },
onHover: (bool hover) { },
),
),
),
),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Container)));
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
});
testWidgets('ink response changes color on focus', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
......@@ -155,6 +192,127 @@ void main() {
..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff0000ff)));
});
testWidgets('ink response changes color on focus with overlayColor', (WidgetTester tester) async {
// Same test as 'ink well changes color on focus' except that the
// hover color is specified with the overlayColor parameter.
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
await tester.pumpWidget(
Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Container(
width: 100,
height: 100,
child: InkWell(
focusNode: focusNode,
overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return const Color(0xff00ff00);
if (states.contains(MaterialState.focused))
return const Color(0xff0000ff);
if (states.contains(MaterialState.pressed))
return const Color(0xf00fffff);
return const Color(0xffbadbad); // Shouldn't happen.
}),
highlightColor: const Color(0xf00fffff),
onTap: () { },
onLongPress: () { },
onHover: (bool hover) { },
),
),
),
),
),
);
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paintsExactlyCountTimes(#rect, 0));
focusNode.requestFocus();
await tester.pumpAndSettle();
expect(inkFeatures, paints
..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff0000ff)));
});
testWidgets('ink response splashColor matches splashColor parameter', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
const Color splashColor = Color(0xffff0000);
await tester.pumpWidget(Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Focus(
focusNode: focusNode,
child: Container(
width: 100,
height: 100,
child: InkWell(
hoverColor: const Color(0xff00ff00),
splashColor: splashColor,
focusColor: const Color(0xff0000ff),
highlightColor: const Color(0xf00fffff),
onTap: () { },
onLongPress: () { },
onHover: (bool hover) { },
),
),
),
),
),
));
await tester.pumpAndSettle();
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
await tester.pump(const Duration(milliseconds: 200)); // unconfirmed splash is well underway
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..circle(x: 50, y: 50, color: splashColor));
await gesture.up();
});
testWidgets('ink response splashColor matches resolved overlayColor for MaterialState.pressed', (WidgetTester tester) async {
// Same test as 'ink response splashColor matches splashColor
// parameter' except that the splash color is specified with the
// overlayColor parameter.
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
const Color splashColor = Color(0xffff0000);
await tester.pumpWidget(Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Focus(
focusNode: focusNode,
child: Container(
width: 100,
height: 100,
child: InkWell(
overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return const Color(0xff00ff00);
if (states.contains(MaterialState.focused))
return const Color(0xff0000ff);
if (states.contains(MaterialState.pressed))
return splashColor;
return const Color(0xffbadbad); // Shouldn't happen.
}),
onTap: () { },
onLongPress: () { },
onHover: (bool hover) { },
),
),
),
),
),
));
await tester.pumpAndSettle();
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
await tester.pump(const Duration(milliseconds: 200)); // unconfirmed splash is well underway
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..circle(x: 50, y: 50, color: splashColor));
await gesture.up();
});
testWidgets("ink response doesn't change color on focus when on touch device", (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
......
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