mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
Reland: MouseRegion enter/exit event can be triggered with button pressed (#83253)
* Revert "Revert "MouseRegion enter/exit event can be triggered with button pressed (#81148)" (#81557)"
This commit is contained in:
parent
e1825c5c4c
commit
e708aa64bd
|
@ -278,12 +278,14 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
|||
|
||||
@override // from GestureBinding
|
||||
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
|
||||
if (hitTestResult != null ||
|
||||
event is PointerAddedEvent ||
|
||||
event is PointerRemovedEvent) {
|
||||
assert(event.position != null);
|
||||
_mouseTracker!.updateWithEvent(event, () => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
|
||||
}
|
||||
_mouseTracker!.updateWithEvent(
|
||||
event,
|
||||
// Enter and exit events should be triggered with or without buttons
|
||||
// pressed. When the button is pressed, normal hit test uses a cached
|
||||
// result, but MouseTracker requires that the hit test is re-executed to
|
||||
// update the hovering events.
|
||||
() => (hitTestResult == null || event is PointerMoveEvent) ? renderView.hitTestMouseTrackers(event.position) : hitTestResult,
|
||||
);
|
||||
super.dispatchEvent(event, hitTestResult);
|
||||
}
|
||||
|
||||
|
|
|
@ -289,17 +289,20 @@ class MouseTracker extends ChangeNotifier {
|
|||
/// Trigger a device update with a new event and its corresponding hit test
|
||||
/// result.
|
||||
///
|
||||
/// The [updateWithEvent] indicates that an event has been observed, and
|
||||
/// is called during the handler of the event. The `getResult` should return
|
||||
/// the hit test result at the position of the event.
|
||||
/// The [updateWithEvent] indicates that an event has been observed, and is
|
||||
/// called during the handler of the event. It is typically called by
|
||||
/// [RendererBinding], and should be called with all events received, and let
|
||||
/// [MouseTracker] filter which to react to.
|
||||
///
|
||||
/// The `getResult` is a function to return the hit test result at the
|
||||
/// position of the event. It should not simply return cached hit test
|
||||
/// result, because the cache does not change throughout a tap sequence.
|
||||
void updateWithEvent(PointerEvent event, ValueGetter<HitTestResult> getResult) {
|
||||
assert(event != null);
|
||||
final HitTestResult result = event is PointerRemovedEvent ? HitTestResult() : getResult();
|
||||
assert(result != null);
|
||||
if (event.kind != PointerDeviceKind.mouse)
|
||||
return;
|
||||
if (event is PointerSignalEvent)
|
||||
return;
|
||||
final HitTestResult result = event is PointerRemovedEvent ? HitTestResult() : getResult();
|
||||
final int device = event.device;
|
||||
final _MouseState? existingState = _mouseStates[device];
|
||||
if (!_shouldMarkStateDirty(existingState, event))
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
@ -13,4 +14,75 @@ void main() {
|
|||
expect(result, hasOneLineDescription);
|
||||
expect(result.path.first, hasOneLineDescription);
|
||||
});
|
||||
|
||||
testWidgets('A mouse click should only cause one hit test', (WidgetTester tester) async {
|
||||
int hitCount = 0;
|
||||
await tester.pumpWidget(
|
||||
_HitTestCounter(
|
||||
onHitTestCallback: () { hitCount += 1; },
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(tester.getCenter(find.byType(_HitTestCounter)), kind: PointerDeviceKind.mouse);
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.up();
|
||||
|
||||
expect(hitCount, 1);
|
||||
});
|
||||
|
||||
testWidgets('Non-mouse events should not cause movement hit tests', (WidgetTester tester) async {
|
||||
int hitCount = 0;
|
||||
await tester.pumpWidget(
|
||||
_HitTestCounter(
|
||||
onHitTestCallback: () { hitCount += 1; },
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(tester.getCenter(find.byType(_HitTestCounter)), kind: PointerDeviceKind.touch);
|
||||
await gesture.moveBy(const Offset(1, 1));
|
||||
await gesture.up();
|
||||
|
||||
expect(hitCount, 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// The [_HitTestCounter] invokes [onHitTestCallback] every time
|
||||
// [hitTestChildren] is called.
|
||||
class _HitTestCounter extends SingleChildRenderObjectWidget {
|
||||
const _HitTestCounter({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
required this.onHitTestCallback,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final VoidCallback? onHitTestCallback;
|
||||
|
||||
@override
|
||||
_RenderHitTestCounter createRenderObject(BuildContext context) {
|
||||
return _RenderHitTestCounter()
|
||||
.._onHitTestCallback = onHitTestCallback;
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
_RenderHitTestCounter renderObject,
|
||||
) {
|
||||
renderObject._onHitTestCallback = onHitTestCallback;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderHitTestCounter extends RenderProxyBox {
|
||||
VoidCallback? _onHitTestCallback;
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
||||
_onHitTestCallback?.call();
|
||||
return super.hitTestChildren(result, position: position);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,42 @@ class _HoverFeedbackState extends State<HoverFeedback> {
|
|||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('onEnter and onExit can be triggered with mouse buttons pressed', (WidgetTester tester) async {
|
||||
PointerEnterEvent? enter;
|
||||
PointerExitEvent? exit;
|
||||
await tester.pumpWidget(Center(
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
color: const Color.fromARGB(0xff, 0xff, 0x00, 0x00),
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, buttons: kPrimaryMouseButton);
|
||||
await gesture.addPointer(location: Offset.zero);
|
||||
await gesture.down(Offset.zero); // Press the mouse button.
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump();
|
||||
enter = null;
|
||||
exit = null;
|
||||
// Trigger the enter event.
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
expect(enter, isNotNull);
|
||||
expect(enter!.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(enter!.localPosition, equals(const Offset(50.0, 50.0)));
|
||||
expect(exit, isNull);
|
||||
|
||||
// Trigger the exit event.
|
||||
await gesture.moveTo(const Offset(1.0, 1.0));
|
||||
expect(exit, isNotNull);
|
||||
expect(exit!.position, equals(const Offset(1.0, 1.0)));
|
||||
expect(exit!.localPosition, equals(const Offset(-349.0, -249.0)));
|
||||
});
|
||||
|
||||
testWidgets('detects pointer enter', (WidgetTester tester) async {
|
||||
PointerEnterEvent? enter;
|
||||
PointerHoverEvent? move;
|
||||
|
|
Loading…
Reference in a new issue