Unverified Commit 4500a451 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Enable InkWell to sync its hovered state when its enabled or disabled (#62913)

parent e86caf7a
......@@ -773,7 +773,11 @@ class _InkResponseState extends State<_InkResponseStateWidget>
void didUpdateWidget(_InkResponseStateWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
_handleHoverChange(_hovering);
if (enabled) {
// Don't call wigdet.onHover because many wigets, including the button
// widgets, apply setState to an ancestor context from onHover.
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
}
_updateFocusHighlights();
}
}
......@@ -818,7 +822,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
return null;
}
void updateHighlight(_HighlightType type, {@required bool value}) {
void updateHighlight(_HighlightType type, { @required bool value, bool callOnHover = true }) {
final InkHighlight highlight = _highlights[type];
void handleInkRemoval() {
assert(_highlights[type] != null);
......@@ -862,7 +866,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
widget.onHighlightChanged(value);
break;
case _HighlightType.hover:
if (widget.onHover != null)
if (callOnHover && widget.onHover != null)
widget.onHover(value);
break;
case _HighlightType.focus:
......@@ -1040,15 +1044,24 @@ class _InkResponseState extends State<_InkResponseStateWidget>
bool get enabled => _isWidgetEnabled(widget);
void _handleMouseEnter(PointerEnterEvent event) => _handleHoverChange(true);
void _handleMouseExit(PointerExitEvent event) => _handleHoverChange(false);
void _handleHoverChange(bool hovering) {
if (_hovering != hovering) {
_hovering = hovering;
updateHighlight(_HighlightType.hover, value: enabled && _hovering);
void _handleMouseEnter(PointerEnterEvent event) {
_hovering = true;
if (enabled) {
_handleHoverChange();
}
}
void _handleMouseExit(PointerExitEvent event) {
_hovering = false;
// If the exit occurs after we've been disabled, we still
// want to take down the highlights and run widget.onHover.
_handleHoverChange();
}
void _handleHoverChange() {
updateHighlight(_HighlightType.hover, value: _hovering);
}
bool get _canRequestFocus {
final NavigationMode mode = MediaQuery.of(context, nullOk: true)?.navigationMode ?? NavigationMode.traditional;
switch (mode) {
......@@ -1076,7 +1089,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
<MaterialState>{
if (!enabled) MaterialState.disabled,
if (_hovering) MaterialState.hovered,
if (_hovering && enabled) MaterialState.hovered,
if (_hasFocus) MaterialState.focused,
},
);
......@@ -1091,8 +1104,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
autofocus: widget.autofocus,
child: MouseRegion(
cursor: effectiveMouseCursor,
onEnter: enabled ? _handleMouseEnter : null,
onExit: enabled ? _handleMouseExit : null,
onEnter: _handleMouseEnter,
onExit: _handleMouseExit,
child: GestureDetector(
onTapDown: enabled ? _handleTapDown : null,
onTap: enabled ? () => _handleTap(context) : null,
......
......@@ -1191,4 +1191,103 @@ void main() {
await tester.pump(const Duration(milliseconds: 200));
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
});
testWidgets('disabled and hovered inkwell responds to mouse-exit', (WidgetTester tester) async {
int onHoverCount = 0;
bool hover;
Widget buildFrame({ bool enabled }) {
return Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: SizedBox(
width: 100,
height: 100,
child: InkWell(
onTap: enabled ? () { } : null,
onHover: (bool value) {
onHoverCount += 1;
hover = value;
},
),
),
),
),
);
}
await tester.pumpWidget(buildFrame(enabled: true));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(InkWell)));
await tester.pumpAndSettle();
expect(onHoverCount, 1);
expect(hover, true);
await tester.pumpWidget(buildFrame(enabled: false));
await tester.pumpAndSettle();
await gesture.moveTo(Offset.zero);
// Even though the InkWell has been disabled, the mouse-exit still
// causes onHover(false) to be called.
expect(onHoverCount, 2);
expect(hover, false);
await gesture.moveTo(tester.getCenter(find.byType(InkWell)));
await tester.pumpAndSettle();
// We no longer see hover events because the InkWell is disabled
// and it's no longer in the "hovering" state.
expect(onHoverCount, 2);
expect(hover, false);
await tester.pumpWidget(buildFrame(enabled: true));
await tester.pumpAndSettle();
// The InkWell was enabled while it contained the mouse, however
// we do not call onHover() because it may call setState().
expect(onHoverCount, 2);
expect(hover, false);
await gesture.moveTo(tester.getCenter(find.byType(InkWell)) - const Offset(1, 1));
await tester.pumpAndSettle();
// Moving the mouse a little within the InkWell doesn't change anything.
expect(onHoverCount, 2);
expect(hover, false);
});
testWidgets('Changing InkWell.enabled should not trigger TextButton setState()', (WidgetTester tester) async {
Widget buildFrame({ bool enabled }) {
return Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: TextButton(
onPressed: enabled ? () { } : null,
child: const Text('button'),
),
),
),
);
}
await tester.pumpWidget(buildFrame(enabled: false));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
await tester.pumpAndSettle();
// Rebuilding the button with enabled:true causes InkWell.didUpdateWidget()
// to be called per the change in its enabled flag. If onHover() was called,
// this test would crash.
await tester.pumpWidget(buildFrame(enabled: true));
await tester.pumpAndSettle();
// Rebuild again, with enabled:false
await gesture.moveBy(const Offset(1, 1));
await tester.pumpWidget(buildFrame(enabled: false));
await tester.pumpAndSettle();
});
}
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