blob: b22da890a7dd4968ede4b488380927ab43bbf612 [file] [log] [blame]
// Copyright 2018 The Chromium 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:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import '../flutter_test_alternative.dart';
typedef HandleEventCallback = void Function(PointerEvent event);
class TestGestureFlutterBinding extends BindingBase with ServicesBinding, SchedulerBinding, GestureBinding {
HandleEventCallback callback;
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
super.handleEvent(event, entry);
if (callback != null) {
callback(event);
}
}
}
TestGestureFlutterBinding _binding = TestGestureFlutterBinding();
void ensureTestGestureBinding() {
_binding ??= TestGestureFlutterBinding();
assert(GestureBinding.instance != null);
}
void main() {
setUp(ensureTestGestureBinding);
group(MouseTracker, () {
final List<PointerEnterEvent> enter = <PointerEnterEvent>[];
final List<PointerHoverEvent> move = <PointerHoverEvent>[];
final List<PointerExitEvent> exit = <PointerExitEvent>[];
final MouseTrackerAnnotation annotation = MouseTrackerAnnotation(
onEnter: (PointerEnterEvent event) => enter.add(event),
onHover: (PointerHoverEvent event) => move.add(event),
onExit: (PointerExitEvent event) => exit.add(event),
);
// Only respond to some mouse events.
final MouseTrackerAnnotation partialAnnotation = MouseTrackerAnnotation(
onEnter: (PointerEnterEvent event) => enter.add(event),
onHover: (PointerHoverEvent event) => move.add(event),
);
bool isInHitRegionOne;
bool isInHitRegionTwo;
MouseTracker tracker;
void clear() {
enter.clear();
exit.clear();
move.clear();
}
setUp(() {
clear();
isInHitRegionOne = true;
isInHitRegionTwo = false;
tracker = MouseTracker(
GestureBinding.instance.pointerRouter,
(Offset _) sync* {
if (isInHitRegionOne)
yield annotation;
else if (isInHitRegionTwo)
yield partialAnnotation;
},
);
});
test('receives and processes mouse hover events', () {
final ui.PointerDataPacket packet1 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 0.0 * ui.window.devicePixelRatio,
physicalY: 0.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet2 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 101.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet3 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.remove,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 201.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet4 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 301.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet5 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 401.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
device: 1,
),
]);
tracker.attachAnnotation(annotation);
isInHitRegionOne = true;
ui.window.onPointerDataPacket(packet1);
tracker.collectMousePositions();
expect(enter.length, equals(1), reason: 'enter contains $enter');
expect(enter.first.position, equals(const Offset(0.0, 0.0)));
expect(enter.first.device, equals(0));
expect(enter.first.runtimeType, equals(PointerEnterEvent));
expect(exit.length, equals(0), reason: 'exit contains $exit');
expect(move.length, equals(1), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(0.0, 0.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
clear();
ui.window.onPointerDataPacket(packet2);
tracker.collectMousePositions();
expect(enter.length, equals(0), reason: 'enter contains $enter');
expect(exit.length, equals(0), reason: 'exit contains $exit');
expect(move.length, equals(1), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(1.0, 101.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
clear();
ui.window.onPointerDataPacket(packet3);
tracker.collectMousePositions();
expect(enter.length, equals(0), reason: 'enter contains $enter');
expect(move.length, equals(0), reason: 'move contains $move');
expect(exit.length, equals(1), reason: 'exit contains $exit');
expect(exit.first.position, equals(const Offset(1.0, 201.0)));
expect(exit.first.device, equals(0));
expect(exit.first.runtimeType, equals(PointerExitEvent));
clear();
ui.window.onPointerDataPacket(packet4);
tracker.collectMousePositions();
expect(enter.length, equals(1), reason: 'enter contains $enter');
expect(enter.first.position, equals(const Offset(1.0, 301.0)));
expect(enter.first.device, equals(0));
expect(enter.first.runtimeType, equals(PointerEnterEvent));
expect(exit.length, equals(0), reason: 'exit contains $exit');
expect(move.length, equals(1), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(1.0, 301.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
// add in a second mouse simultaneously.
clear();
ui.window.onPointerDataPacket(packet5);
tracker.collectMousePositions();
expect(enter.length, equals(1), reason: 'enter contains $enter');
expect(enter.first.position, equals(const Offset(1.0, 401.0)));
expect(enter.first.device, equals(1));
expect(enter.first.runtimeType, equals(PointerEnterEvent));
expect(exit.length, equals(0), reason: 'exit contains $exit');
expect(move.length, equals(2), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(1.0, 301.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
expect(move.last.position, equals(const Offset(1.0, 401.0)));
expect(move.last.device, equals(1));
expect(move.last.runtimeType, equals(PointerHoverEvent));
});
test('detects exit when annotated layer no longer hit', () {
final ui.PointerDataPacket packet1 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 0.0 * ui.window.devicePixelRatio,
physicalY: 0.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 101.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet2 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 201.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
isInHitRegionOne = true;
tracker.attachAnnotation(annotation);
ui.window.onPointerDataPacket(packet1);
tracker.collectMousePositions();
expect(enter.length, equals(1), reason: 'enter contains $enter');
expect(enter.first.position, equals(const Offset(1.0, 101.0)));
expect(enter.first.device, equals(0));
expect(enter.first.runtimeType, equals(PointerEnterEvent));
expect(move.length, equals(1), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(1.0, 101.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
expect(exit.length, equals(0), reason: 'exit contains $exit');
// Simulate layer going away by detaching it.
clear();
isInHitRegionOne = false;
ui.window.onPointerDataPacket(packet2);
tracker.collectMousePositions();
expect(enter.length, equals(0), reason: 'enter contains $enter');
expect(move.length, equals(0), reason: 'enter contains $move');
expect(exit.length, equals(1), reason: 'enter contains $exit');
expect(exit.first.position, const Offset(1.0, 201.0));
expect(exit.first.device, equals(0));
expect(exit.first.runtimeType, equals(PointerExitEvent));
// Actually detach annotation. Shouldn't receive hit.
tracker.detachAnnotation(annotation);
clear();
isInHitRegionOne = false;
ui.window.onPointerDataPacket(packet2);
tracker.collectMousePositions();
expect(enter.length, equals(0), reason: 'enter contains $enter');
expect(move.length, equals(0), reason: 'enter contains $move');
expect(exit.length, equals(0), reason: 'enter contains $exit');
});
test("don't flip out if not all mouse events are listened to", () {
final ui.PointerDataPacket packet = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 101.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
isInHitRegionOne = false;
isInHitRegionTwo = true;
tracker.attachAnnotation(partialAnnotation);
ui.window.onPointerDataPacket(packet);
tracker.collectMousePositions();
tracker.detachAnnotation(partialAnnotation);
isInHitRegionTwo = false;
});
test('detects exit when mouse goes away', () {
final ui.PointerDataPacket packet1 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 0.0 * ui.window.devicePixelRatio,
physicalY: 0.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 101.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet2 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.remove,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 201.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
isInHitRegionOne = true;
tracker.attachAnnotation(annotation);
ui.window.onPointerDataPacket(packet1);
tracker.collectMousePositions();
ui.window.onPointerDataPacket(packet2);
tracker.collectMousePositions();
expect(enter.length, equals(1), reason: 'enter contains $enter');
expect(enter.first.position, equals(const Offset(1.0, 101.0)));
expect(enter.first.delta, equals(const Offset(1.0, 101.0)));
expect(enter.first.device, equals(0));
expect(enter.first.runtimeType, equals(PointerEnterEvent));
expect(move.length, equals(1), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(1.0, 101.0)));
expect(move.first.delta, equals(const Offset(1.0, 101.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
expect(exit.length, equals(1), reason: 'exit contains $exit');
expect(exit.first.position, equals(const Offset(1.0, 201.0)));
expect(exit.first.delta, equals(const Offset(0.0, 0.0)));
expect(exit.first.device, equals(0));
expect(exit.first.runtimeType, equals(PointerExitEvent));
});
test('handles mouse down and move', () {
final ui.PointerDataPacket packet1 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 0.0 * ui.window.devicePixelRatio,
physicalY: 0.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
ui.PointerData(
change: ui.PointerChange.hover,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 101.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
final ui.PointerDataPacket packet2 = ui.PointerDataPacket(data: <ui.PointerData>[
ui.PointerData(
change: ui.PointerChange.down,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 101.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
ui.PointerData(
change: ui.PointerChange.move,
physicalX: 1.0 * ui.window.devicePixelRatio,
physicalY: 201.0 * ui.window.devicePixelRatio,
kind: PointerDeviceKind.mouse,
),
]);
isInHitRegionOne = true;
tracker.attachAnnotation(annotation);
ui.window.onPointerDataPacket(packet1);
tracker.collectMousePositions();
ui.window.onPointerDataPacket(packet2);
tracker.collectMousePositions();
expect(enter.length, equals(1), reason: 'enter contains $enter');
expect(enter.first.position, equals(const Offset(1.0, 101.0)));
expect(enter.first.device, equals(0));
expect(enter.first.runtimeType, equals(PointerEnterEvent));
expect(move.length, equals(1), reason: 'move contains $move');
expect(move.first.position, equals(const Offset(1.0, 101.0)));
expect(move.first.device, equals(0));
expect(move.first.runtimeType, equals(PointerHoverEvent));
expect(exit.length, equals(0), reason: 'exit contains $exit');
});
});
}