mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
parent
2f937703e4
commit
02612bfe32
@ -26,6 +26,7 @@ export 'src/gestures/multitap.dart';
|
||||
export 'src/gestures/pointer_router.dart';
|
||||
export 'src/gestures/pointer_signal_resolver.dart';
|
||||
export 'src/gestures/recognizer.dart';
|
||||
export 'src/gestures/resampler.dart';
|
||||
export 'src/gestures/scale.dart';
|
||||
export 'src/gestures/tap.dart';
|
||||
export 'src/gestures/team.dart';
|
||||
|
||||
@ -8,6 +8,7 @@ import 'dart:collection';
|
||||
import 'dart:ui' as ui show PointerDataPacket;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import 'converter.dart';
|
||||
@ -16,6 +17,131 @@ import 'events.dart';
|
||||
import 'hit_test.dart';
|
||||
import 'pointer_router.dart';
|
||||
import 'pointer_signal_resolver.dart';
|
||||
import 'resampler.dart';
|
||||
|
||||
typedef HandleSampleTimeChangedCallback = void Function();
|
||||
|
||||
// Class that handles resampling of touch events for multiple pointer
|
||||
// devices.
|
||||
//
|
||||
// SchedulerBinding's `currentSystemFrameTimeStamp` is used to determine
|
||||
// sample time.
|
||||
class _Resampler {
|
||||
_Resampler(this._handlePointerEvent, this._handleSampleTimeChanged);
|
||||
|
||||
// Resamplers used to filter incoming pointer events.
|
||||
final Map<int, PointerEventResampler> _resamplers = <int, PointerEventResampler>{};
|
||||
|
||||
// Flag to track if a frame callback has been scheduled.
|
||||
bool _frameCallbackScheduled = false;
|
||||
|
||||
// Current frame time for resampling.
|
||||
Duration _frameTime = Duration.zero;
|
||||
|
||||
// Last sample time and time stamp of last event.
|
||||
//
|
||||
// Only used for debugPrint of resampling margin.
|
||||
Duration _lastSampleTime = Duration.zero;
|
||||
Duration _lastEventTime = Duration.zero;
|
||||
|
||||
// Callback used to handle pointer events.
|
||||
final HandleEventCallback _handlePointerEvent;
|
||||
|
||||
// Callback used to handle sample time changes.
|
||||
final HandleSampleTimeChangedCallback _handleSampleTimeChanged;
|
||||
|
||||
// Enqueue `events` for resampling or dispatch them directly if
|
||||
// not a touch event.
|
||||
void addOrDispatchAll(Queue<PointerEvent> events) {
|
||||
final SchedulerBinding? scheduler = SchedulerBinding.instance;
|
||||
assert(scheduler != null);
|
||||
|
||||
while (events.isNotEmpty) {
|
||||
final PointerEvent event = events.removeFirst();
|
||||
|
||||
// Add touch event to resampler or dispatch pointer event directly.
|
||||
if (event.kind == PointerDeviceKind.touch) {
|
||||
// Save last event time for debugPrint of resampling margin.
|
||||
_lastEventTime = event.timeStamp;
|
||||
|
||||
final PointerEventResampler resampler = _resamplers.putIfAbsent(
|
||||
event.device,
|
||||
() => PointerEventResampler(),
|
||||
);
|
||||
resampler.addEvent(event);
|
||||
} else {
|
||||
_handlePointerEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sample and dispatch events.
|
||||
//
|
||||
// `samplingOffset` is relative to the current frame time, which
|
||||
// can be in the past when we're not actively resampling.
|
||||
// `currentSystemFrameTimeStamp` is used to determine the current
|
||||
// frame time.
|
||||
void sample(Duration samplingOffset) {
|
||||
final SchedulerBinding? scheduler = SchedulerBinding.instance;
|
||||
assert(scheduler != null);
|
||||
|
||||
// Determine sample time by adding the offset to the current
|
||||
// frame time. This is expected to be in the past and not
|
||||
// result in any dispatched events unless we're actively
|
||||
// resampling events.
|
||||
final Duration sampleTime = _frameTime + samplingOffset;
|
||||
|
||||
// Iterate over active resamplers and sample pointer events for
|
||||
// current sample time.
|
||||
for (final PointerEventResampler resampler in _resamplers.values) {
|
||||
resampler.sample(sampleTime, _handlePointerEvent);
|
||||
}
|
||||
|
||||
// Remove inactive resamplers.
|
||||
_resamplers.removeWhere((int key, PointerEventResampler resampler) {
|
||||
return !resampler.hasPendingEvents && !resampler.isDown;
|
||||
});
|
||||
|
||||
// Save last sample time for debugPrint of resampling margin.
|
||||
_lastSampleTime = sampleTime;
|
||||
|
||||
// Schedule a frame callback if another call to `sample` is needed.
|
||||
if (!_frameCallbackScheduled && _resamplers.isNotEmpty) {
|
||||
_frameCallbackScheduled = true;
|
||||
scheduler?.scheduleFrameCallback((_) {
|
||||
_frameCallbackScheduled = false;
|
||||
// We use `currentSystemFrameTimeStamp` here as it's critical that
|
||||
// sample time is in the same clock as the event time stamps, and
|
||||
// never adjusted or scaled like `currentFrameTimeStamp`.
|
||||
_frameTime = scheduler.currentSystemFrameTimeStamp;
|
||||
assert(() {
|
||||
if (debugPrintResamplingMargin) {
|
||||
final Duration resamplingMargin = _lastEventTime - _lastSampleTime;
|
||||
debugPrint('$resamplingMargin');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
_handleSampleTimeChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Stop all resampling and dispatched any queued events.
|
||||
void stop() {
|
||||
for (final PointerEventResampler resampler in _resamplers.values) {
|
||||
resampler.stop(_handlePointerEvent);
|
||||
}
|
||||
_resamplers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// The default sampling offset.
|
||||
//
|
||||
// Sampling offset is relative to presentation time. If we produce frames
|
||||
// 16.667 ms before presentation and input rate is ~60hz, worst case latency
|
||||
// is 33.334 ms. This however assumes zero latency from the input driver.
|
||||
// 4.666 ms margin is added for this.
|
||||
const Duration _defaultSamplingOffset = Duration(milliseconds: -38);
|
||||
|
||||
/// A binding for the gesture subsystem.
|
||||
///
|
||||
@ -99,6 +225,17 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
||||
|
||||
void _flushPointerEventQueue() {
|
||||
assert(!locked);
|
||||
|
||||
if (resamplingEnabled) {
|
||||
_resampler.addOrDispatchAll(_pendingPointerEvents);
|
||||
_resampler.sample(samplingOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop resampler if resampling is not enabled. This is a no-op if
|
||||
// resampling was never enabled.
|
||||
_resampler.stop();
|
||||
|
||||
while (_pendingPointerEvents.isNotEmpty)
|
||||
_handlePointerEvent(_pendingPointerEvents.removeFirst());
|
||||
}
|
||||
@ -226,6 +363,38 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
||||
pointerSignalResolver.resolve(event);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleSampleTimeChanged() {
|
||||
if (!locked) {
|
||||
_flushPointerEventQueue();
|
||||
}
|
||||
}
|
||||
|
||||
// Resampler used to filter incoming pointer events when resampling
|
||||
// is enabled.
|
||||
late final _Resampler _resampler = _Resampler(
|
||||
_handlePointerEvent,
|
||||
_handleSampleTimeChanged,
|
||||
);
|
||||
|
||||
/// Enable pointer event resampling for touch devices by setting
|
||||
/// this to true.
|
||||
///
|
||||
/// Resampling results in smoother touch event processing at the
|
||||
/// cost of some added latency. Devices with low frequency sensors
|
||||
/// or when the frequency is not a multiple of the display frequency
|
||||
/// (e.g., 120Hz input and 90Hz display) benefit from this.
|
||||
///
|
||||
/// This is typically set during application initialization but
|
||||
/// can be adjusted dynamically in case the application only
|
||||
/// wants resampling for some period of time.
|
||||
bool resamplingEnabled = false;
|
||||
|
||||
/// Offset relative to current frame time that should be used for
|
||||
/// resampling. The [samplingOffset] is expected to be negative.
|
||||
/// Non-negative [samplingOffset] is allowed but will effectively
|
||||
/// disable resampling.
|
||||
Duration samplingOffset = _defaultSamplingOffset;
|
||||
}
|
||||
|
||||
/// Variant of [FlutterErrorDetails] with extra fields for the gesture
|
||||
|
||||
@ -50,6 +50,20 @@ bool debugPrintGestureArenaDiagnostics = false;
|
||||
/// arenas.
|
||||
bool debugPrintRecognizerCallbacksTrace = false;
|
||||
|
||||
/// Whether to print the resampling margin to the console.
|
||||
///
|
||||
/// When this is set, in debug mode, any time resampling is triggered by the
|
||||
/// [GestureBinding] the resampling margin is dumped to the console. The
|
||||
/// resampling margin is the delta between the time of the last received
|
||||
/// touch event and the current sample time. Positive value indicates that
|
||||
/// resampling is effective and the resampling offset can potentially be
|
||||
/// reduced for improved latency. Negative value indicates that resampling
|
||||
/// is failing and resampling offset needs to be increased for smooth
|
||||
/// touch event processing.
|
||||
///
|
||||
/// This has no effect in release builds.
|
||||
bool debugPrintResamplingMargin = false;
|
||||
|
||||
/// Returns true if none of the gestures library debug variables have been changed.
|
||||
///
|
||||
/// This function is used by the test framework to ensure that debug variables
|
||||
@ -61,7 +75,8 @@ bool debugAssertAllGesturesVarsUnset(String reason) {
|
||||
assert(() {
|
||||
if (debugPrintHitTestResults ||
|
||||
debugPrintGestureArenaDiagnostics ||
|
||||
debugPrintRecognizerCallbacksTrace)
|
||||
debugPrintRecognizerCallbacksTrace ||
|
||||
debugPrintResamplingMargin)
|
||||
throw FlutterError(reason);
|
||||
return true;
|
||||
}());
|
||||
|
||||
@ -517,6 +517,36 @@ abstract class PointerEvent with Diagnosticable {
|
||||
/// overridden by the provided `transform`.
|
||||
PointerEvent transformed(Matrix4? transform);
|
||||
|
||||
/// Creates a copy of event with the specified properties replaced.
|
||||
PointerEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
});
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
@ -682,6 +712,56 @@ class PointerAddedEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerAddedEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerAddedEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distance: distance ?? this.distance,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerAddedEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The device is no longer tracking the pointer.
|
||||
@ -747,6 +827,53 @@ class PointerRemovedEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerRemovedEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerRemovedEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerRemovedEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer has moved with respect to the device while the pointer is not
|
||||
@ -857,6 +984,63 @@ class PointerHoverEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerHoverEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerHoverEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
delta: delta ?? this.delta,
|
||||
localDelta: localDelta ?? this.localDelta,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distance: distance ?? this.distance,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
synthesized: synthesized ?? this.synthesized,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerHoverEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer has moved with respect to the device while the pointer is or is
|
||||
@ -1008,6 +1192,63 @@ class PointerEnterEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerEnterEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerEnterEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
delta: delta ?? this.delta,
|
||||
localDelta: localDelta ?? this.localDelta,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distance: distance ?? this.distance,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
synthesized: synthesized ?? this.synthesized,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerEnterEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer has moved with respect to the device while the pointer is or is
|
||||
@ -1159,6 +1400,63 @@ class PointerExitEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerExitEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerExitEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
delta: delta ?? this.delta,
|
||||
localDelta: localDelta ?? this.localDelta,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distance: distance ?? this.distance,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
synthesized: synthesized ?? this.synthesized,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerExitEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer has made contact with the device.
|
||||
@ -1251,6 +1549,61 @@ class PointerDownEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerDownEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerDownEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
pointer: pointer ?? this.pointer,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressure: pressure ?? this.pressure,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerDownEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer has moved with respect to the device while the pointer is in
|
||||
@ -1365,6 +1718,64 @@ class PointerMoveEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerMoveEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerMoveEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
pointer: pointer ?? this.pointer,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
delta: delta ?? this.delta,
|
||||
localDelta: localDelta ?? this.localDelta,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressure: pressure ?? this.pressure,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
synthesized: synthesized ?? this.synthesized,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerMoveEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer has stopped making contact with the device.
|
||||
@ -1461,6 +1872,62 @@ class PointerUpEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerUpEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerUpEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
pointer: pointer ?? this.pointer,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressure: pressure ?? this.pressure,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distance: distance ?? this.distance,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerUpEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that corresponds to a discrete pointer signal.
|
||||
@ -1559,6 +2026,48 @@ class PointerScrollEvent extends PointerSignalEvent {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerScrollEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerScrollEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
scrollDelta: scrollDelta,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerScrollEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
@ -1656,4 +2165,59 @@ class PointerCancelEvent extends PointerEvent {
|
||||
embedderId: embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PointerCancelEvent copyWith({
|
||||
Duration? timeStamp,
|
||||
int? pointer,
|
||||
PointerDeviceKind? kind,
|
||||
int? device,
|
||||
Offset? position,
|
||||
Offset? localPosition,
|
||||
Offset? delta,
|
||||
Offset? localDelta,
|
||||
int? buttons,
|
||||
bool? obscured,
|
||||
double? pressure,
|
||||
double? pressureMin,
|
||||
double? pressureMax,
|
||||
double? distance,
|
||||
double? distanceMax,
|
||||
double? size,
|
||||
double? radiusMajor,
|
||||
double? radiusMinor,
|
||||
double? radiusMin,
|
||||
double? radiusMax,
|
||||
double? orientation,
|
||||
double? tilt,
|
||||
bool? synthesized,
|
||||
Matrix4? transform,
|
||||
PointerEvent? original,
|
||||
int? embedderId,
|
||||
}) {
|
||||
return PointerCancelEvent(
|
||||
timeStamp: timeStamp ?? this.timeStamp,
|
||||
pointer: pointer ?? this.pointer,
|
||||
kind: kind ?? this.kind,
|
||||
device: device ?? this.device,
|
||||
position: position ?? this.position,
|
||||
localPosition: localPosition ?? this.localPosition,
|
||||
buttons: buttons ?? this.buttons,
|
||||
obscured: obscured ?? this.obscured,
|
||||
pressureMin: pressureMin ?? this.pressureMin,
|
||||
pressureMax: pressureMax ?? this.pressureMax,
|
||||
distance: distance ?? this.distance,
|
||||
distanceMax: distanceMax ?? this.distanceMax,
|
||||
size: size ?? this.size,
|
||||
radiusMajor: radiusMajor ?? this.radiusMajor,
|
||||
radiusMinor: radiusMinor ?? this.radiusMinor,
|
||||
radiusMin: radiusMin ?? this.radiusMin,
|
||||
radiusMax: radiusMax ?? this.radiusMax,
|
||||
orientation: orientation ?? this.orientation,
|
||||
tilt: tilt ?? this.tilt,
|
||||
transform: transform ?? this.transform,
|
||||
original: original as PointerCancelEvent? ?? this,
|
||||
embedderId: embedderId ?? this.embedderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
305
packages/flutter/lib/src/gestures/resampler.dart
Normal file
305
packages/flutter/lib/src/gestures/resampler.dart
Normal file
@ -0,0 +1,305 @@
|
||||
// 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 'dart:collection';
|
||||
|
||||
import 'events.dart';
|
||||
|
||||
typedef HandleEventCallback = void Function(PointerEvent event);
|
||||
|
||||
/// Class for pointer event resampling.
|
||||
///
|
||||
/// An instance of this class can be used to resample one sequence
|
||||
/// of pointer events. Multiple instances are expected to be used for
|
||||
/// multi-touch support. The sampling frequency and the sampling
|
||||
/// offset is determined by the caller.
|
||||
///
|
||||
/// This can be used to get smooth touch event processing at the cost
|
||||
/// of adding some latency. Devices with low frequency sensors or when
|
||||
/// the frequency is not a multiple of the display frequency
|
||||
/// (e.g., 120Hz input and 90Hz display) benefit from this.
|
||||
///
|
||||
/// The following pointer event types are supported:
|
||||
/// [PointerAddedEvent], [PointerHoverEvent], [PointerDownEvent],
|
||||
/// [PointerMoveEvent], [PointerCancelEvent], [PointerUpEvent],
|
||||
/// [PointerRemovedEvent].
|
||||
///
|
||||
/// Resampling is currently limited to event position and delta. All
|
||||
/// pointer event types except [PointerAddedEvent] will be resampled.
|
||||
/// [PointerHoverEvent] and [PointerMoveEvent] will only be generated
|
||||
/// when the position has changed.
|
||||
class PointerEventResampler {
|
||||
// Events queued for processing.
|
||||
final Queue<PointerEvent> _queuedEvents = Queue<PointerEvent>();
|
||||
|
||||
// Pointer state required for resampling.
|
||||
PointerEvent? _last;
|
||||
PointerEvent? _next;
|
||||
Offset _position = Offset.zero;
|
||||
bool _isTracked = false;
|
||||
bool _isDown = false;
|
||||
int _pointerIdentifier = 0;
|
||||
|
||||
PointerEvent _toHoverEvent(
|
||||
PointerEvent event,
|
||||
Offset position,
|
||||
Offset delta,
|
||||
Duration timeStamp,
|
||||
) {
|
||||
return PointerHoverEvent(
|
||||
timeStamp: timeStamp,
|
||||
kind: event.kind,
|
||||
device: event.device,
|
||||
position: position,
|
||||
delta: delta,
|
||||
buttons: event.buttons,
|
||||
obscured: event.obscured,
|
||||
pressureMin: event.pressureMin,
|
||||
pressureMax: event.pressureMax,
|
||||
distance: event.distance,
|
||||
distanceMax: event.distanceMax,
|
||||
size: event.size,
|
||||
radiusMajor: event.radiusMajor,
|
||||
radiusMinor: event.radiusMinor,
|
||||
radiusMin: event.radiusMin,
|
||||
radiusMax: event.radiusMax,
|
||||
orientation: event.orientation,
|
||||
tilt: event.tilt,
|
||||
synthesized: event.synthesized,
|
||||
embedderId: event.embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
PointerEvent _toMoveEvent(
|
||||
PointerEvent event,
|
||||
Offset position,
|
||||
Offset delta,
|
||||
int pointerIdentifier,
|
||||
Duration timeStamp,
|
||||
) {
|
||||
return PointerMoveEvent(
|
||||
timeStamp: timeStamp,
|
||||
pointer: pointerIdentifier,
|
||||
kind: event.kind,
|
||||
device: event.device,
|
||||
position: position,
|
||||
delta: delta,
|
||||
buttons: event.buttons,
|
||||
obscured: event.obscured,
|
||||
pressure: event.pressure,
|
||||
pressureMin: event.pressureMin,
|
||||
pressureMax: event.pressureMax,
|
||||
distanceMax: event.distanceMax,
|
||||
size: event.size,
|
||||
radiusMajor: event.radiusMajor,
|
||||
radiusMinor: event.radiusMinor,
|
||||
radiusMin: event.radiusMin,
|
||||
radiusMax: event.radiusMax,
|
||||
orientation: event.orientation,
|
||||
tilt: event.tilt,
|
||||
platformData: event.platformData,
|
||||
synthesized: event.synthesized,
|
||||
embedderId: event.embedderId,
|
||||
);
|
||||
}
|
||||
|
||||
Offset _positionAt(Duration sampleTime) {
|
||||
// Use `next` position by default.
|
||||
double x = _next?.position.dx ?? 0.0;
|
||||
double y = _next?.position.dy ?? 0.0;
|
||||
|
||||
final Duration nextTimeStamp = _next?.timeStamp ?? Duration.zero;
|
||||
final Duration lastTimeStamp = _last?.timeStamp ?? Duration.zero;
|
||||
|
||||
// Resample if `next` time stamp is past `sampleTime`.
|
||||
if (nextTimeStamp > sampleTime && nextTimeStamp > lastTimeStamp) {
|
||||
final double interval = (nextTimeStamp - lastTimeStamp).inMicroseconds.toDouble();
|
||||
final double scalar = (sampleTime - lastTimeStamp).inMicroseconds.toDouble() / interval;
|
||||
final double lastX = _last?.position.dx ?? 0.0;
|
||||
final double lastY = _last?.position.dy ?? 0.0;
|
||||
x = lastX + (x - lastX) * scalar;
|
||||
y = lastY + (y - lastY) * scalar;
|
||||
}
|
||||
|
||||
return Offset(x, y);
|
||||
}
|
||||
|
||||
void _processPointerEvents(Duration sampleTime) {
|
||||
final Iterator<PointerEvent> it = _queuedEvents.iterator;
|
||||
while (it.moveNext()) {
|
||||
final PointerEvent event = it.current;
|
||||
|
||||
// Update both `last` and `next` pointer event if time stamp is older
|
||||
// or equal to `sampleTime`.
|
||||
if (event.timeStamp <= sampleTime || _last == null) {
|
||||
_last = event;
|
||||
_next = event;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update only `next` pointer event if time stamp is more recent than
|
||||
// `sampleTime` and next event is not already more recent.
|
||||
final Duration nextTimeStamp = _next?.timeStamp ?? Duration.zero;
|
||||
if (nextTimeStamp < sampleTime) {
|
||||
_next = event;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _dequeueAndSampleNonHoverOrMovePointerEventsUntil(
|
||||
Duration sampleTime,
|
||||
HandleEventCallback callback,
|
||||
) {
|
||||
while (_queuedEvents.isNotEmpty) {
|
||||
final PointerEvent event = _queuedEvents.first;
|
||||
|
||||
// Potentially stop dispatching events if more recent than `sampleTime`.
|
||||
if (event.timeStamp > sampleTime) {
|
||||
// Stop if event is not up or removed. Otherwise, continue to
|
||||
// allow early processing of up and remove events as this improves
|
||||
// resampling of these events, which is important for fling
|
||||
// animations.
|
||||
if (event is! PointerUpEvent && event is! PointerRemovedEvent) {
|
||||
break;
|
||||
}
|
||||
|
||||
// When this line is reached, the following two invariants hold:
|
||||
// (1) `event.timeStamp > sampleTime`
|
||||
// (2) `_next` has the smallest time stamp that's no less than
|
||||
// `sampleTime`
|
||||
//
|
||||
// Therefore, event must satisfy `event.timeStamp >= _next.timeStamp`.
|
||||
//
|
||||
// Those events with the minimum `event.timeStamp == _next.timeStamp`
|
||||
// time stamp are processed early for smoother fling. For events with
|
||||
// `event.timeStamp > _next.timeStamp`, the following lines break the
|
||||
// while loop to stop the early processing.
|
||||
//
|
||||
// Specifically, when `sampleTime < _next.timeStamp`, there must be
|
||||
// at least one event with `_next.timeStamp == event.timeStamp`
|
||||
// and that event is `_next` itself, and it will be processed early.
|
||||
//
|
||||
// When `sampleTime == _next.timeStamp`, all events with
|
||||
// `event.timeStamp > sampleTime` must also have
|
||||
// `event.timeStamp > _next.timeStamp` so no events will be processed
|
||||
// early.
|
||||
//
|
||||
// When the input frequency is no greater than the sampling
|
||||
// frequency, this early processing should guarantee that `up` and
|
||||
// `remove` events are always re-sampled.
|
||||
final Duration nextTimeStamp = _next?.timeStamp ?? Duration.zero;
|
||||
assert(event.timeStamp >= nextTimeStamp);
|
||||
if (event.timeStamp > nextTimeStamp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final bool wasTracked = _isTracked;
|
||||
final bool wasDown = _isDown;
|
||||
|
||||
// Update pointer state.
|
||||
_isTracked = event is! PointerRemovedEvent;
|
||||
_isDown = event.down;
|
||||
|
||||
// Position at `sampleTime`.
|
||||
final Offset position = _positionAt(sampleTime);
|
||||
|
||||
// Initialize position if we are starting to track this pointer.
|
||||
if (_isTracked && !wasTracked) {
|
||||
_position = position;
|
||||
}
|
||||
|
||||
// Current pointer identifier.
|
||||
final int pointerIdentifier = event.pointer;
|
||||
|
||||
// Initialize pointer identifier for `move` events.
|
||||
// Identifier is expected to be the same while `down`.
|
||||
assert(!wasDown || _pointerIdentifier == pointerIdentifier);
|
||||
_pointerIdentifier = pointerIdentifier;
|
||||
|
||||
// Skip `move` and `hover` events as they are automatically
|
||||
// generated when the position has changed.
|
||||
if (event is! PointerMoveEvent && event is! PointerHoverEvent) {
|
||||
callback(event.copyWith(
|
||||
position: position,
|
||||
delta: position - _position,
|
||||
pointer: pointerIdentifier,
|
||||
timeStamp: sampleTime,
|
||||
));
|
||||
_position = position;
|
||||
}
|
||||
|
||||
_queuedEvents.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
void _samplePointerPosition(
|
||||
Duration sampleTime,
|
||||
HandleEventCallback callback,
|
||||
) {
|
||||
// Position at `sampleTime`.
|
||||
final Offset position = _positionAt(sampleTime);
|
||||
|
||||
// Add `move` or `hover` events if position has changed.
|
||||
final PointerEvent? next = _next;
|
||||
if (position != _position && next != null) {
|
||||
final Offset delta = position - _position;
|
||||
final PointerEvent event = _isDown
|
||||
? _toMoveEvent(next, position, delta, _pointerIdentifier, sampleTime)
|
||||
: _toHoverEvent(next, position, delta, sampleTime);
|
||||
callback(event);
|
||||
_position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue pointer `event` for resampling.
|
||||
void addEvent(PointerEvent event) {
|
||||
_queuedEvents.add(event);
|
||||
}
|
||||
|
||||
/// Dispatch resampled pointer events for the specified `sampleTime`
|
||||
/// by calling [callback].
|
||||
///
|
||||
/// This may dispatch multiple events if position is not the only
|
||||
/// state that has changed since last sample.
|
||||
///
|
||||
/// Calling [callback] must not add or sample events.
|
||||
void sample(Duration sampleTime, HandleEventCallback callback) {
|
||||
_processPointerEvents(sampleTime);
|
||||
|
||||
// Dequeue and sample pointer events until `sampleTime`.
|
||||
_dequeueAndSampleNonHoverOrMovePointerEventsUntil(sampleTime, callback);
|
||||
|
||||
// Dispatch resampled pointer location event if tracked.
|
||||
if (_isTracked) {
|
||||
_samplePointerPosition(sampleTime, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop resampling.
|
||||
///
|
||||
/// This will dispatch pending events by calling [callback] and reset
|
||||
/// internal state.
|
||||
void stop(HandleEventCallback callback) {
|
||||
while (_queuedEvents.isNotEmpty) {
|
||||
callback(_queuedEvents.removeFirst());
|
||||
}
|
||||
_pointerIdentifier = 0;
|
||||
_isDown = false;
|
||||
_isTracked = false;
|
||||
_position = Offset.zero;
|
||||
_next = null;
|
||||
_last = null;
|
||||
}
|
||||
|
||||
/// Returns `true` if a call to [sample] can dispatch more events.
|
||||
bool get hasPendingEvents => _queuedEvents.isNotEmpty;
|
||||
|
||||
/// Returns `true` if pointer is currently tracked.
|
||||
bool get isTracked => _isTracked;
|
||||
|
||||
/// Returns `true` if pointer is currently down.
|
||||
bool get isDown => _isDown;
|
||||
}
|
||||
@ -498,6 +498,286 @@ void main() {
|
||||
localPosition: localPosition,
|
||||
);
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
const PointerEvent added = PointerAddedEvent(
|
||||
timeStamp: Duration(days: 1),
|
||||
kind: PointerDeviceKind.unknown,
|
||||
device: 10,
|
||||
position: Offset(101.0, 202.0),
|
||||
obscured: true,
|
||||
pressureMax: 2.1,
|
||||
pressureMin: 1.1,
|
||||
distance: 11,
|
||||
distanceMax: 110,
|
||||
radiusMin: 1.1,
|
||||
radiusMax: 22,
|
||||
orientation: 1.1,
|
||||
tilt: 1.1,
|
||||
);
|
||||
const PointerEvent hover = PointerHoverEvent(
|
||||
timeStamp: Duration(days: 1),
|
||||
kind: PointerDeviceKind.unknown,
|
||||
device: 10,
|
||||
position: Offset(101.0, 202.0),
|
||||
buttons: 7,
|
||||
obscured: true,
|
||||
pressureMax: 2.1,
|
||||
pressureMin: 1.1,
|
||||
distance: 11,
|
||||
distanceMax: 110,
|
||||
size: 11,
|
||||
radiusMajor: 11,
|
||||
radiusMinor: 9,
|
||||
radiusMin: 1.1,
|
||||
radiusMax: 22,
|
||||
orientation: 1.1,
|
||||
tilt: 1.1,
|
||||
synthesized: true,
|
||||
);
|
||||
const PointerEvent down = PointerDownEvent(
|
||||
timeStamp: Duration(days: 1),
|
||||
pointer: 1,
|
||||
kind: PointerDeviceKind.unknown,
|
||||
device: 10,
|
||||
position: Offset(101.0, 202.0),
|
||||
buttons: 7,
|
||||
obscured: true,
|
||||
pressureMax: 2.1,
|
||||
pressureMin: 1.1,
|
||||
distanceMax: 110,
|
||||
size: 11,
|
||||
radiusMajor: 11,
|
||||
radiusMinor: 9,
|
||||
radiusMin: 1.1,
|
||||
radiusMax: 22,
|
||||
orientation: 1.1,
|
||||
tilt: 1.1,
|
||||
);
|
||||
const PointerEvent move = PointerMoveEvent(
|
||||
timeStamp: Duration(days: 1),
|
||||
pointer: 1,
|
||||
kind: PointerDeviceKind.unknown,
|
||||
device: 10,
|
||||
position: Offset(101.0, 202.0),
|
||||
delta: Offset(1.0, 2.0),
|
||||
buttons: 7,
|
||||
obscured: true,
|
||||
pressureMax: 2.1,
|
||||
pressureMin: 1.1,
|
||||
distanceMax: 110,
|
||||
size: 11,
|
||||
radiusMajor: 11,
|
||||
radiusMinor: 9,
|
||||
radiusMin: 1.1,
|
||||
radiusMax: 22,
|
||||
orientation: 1.1,
|
||||
tilt: 1.1,
|
||||
synthesized: true,
|
||||
);
|
||||
const PointerEvent up = PointerUpEvent(
|
||||
timeStamp: Duration(days: 1),
|
||||
pointer: 1,
|
||||
kind: PointerDeviceKind.unknown,
|
||||
device: 10,
|
||||
position: Offset(101.0, 202.0),
|
||||
buttons: 7,
|
||||
obscured: true,
|
||||
pressureMax: 2.1,
|
||||
pressureMin: 1.1,
|
||||
distanceMax: 110,
|
||||
size: 11,
|
||||
radiusMajor: 11,
|
||||
radiusMinor: 9,
|
||||
radiusMin: 1.1,
|
||||
radiusMax: 22,
|
||||
orientation: 1.1,
|
||||
tilt: 1.1,
|
||||
);
|
||||
const PointerEvent removed = PointerRemovedEvent(
|
||||
timeStamp: Duration(days: 1),
|
||||
kind: PointerDeviceKind.unknown,
|
||||
device: 10,
|
||||
position: Offset(101.0, 202.0),
|
||||
obscured: true,
|
||||
pressureMax: 2.1,
|
||||
pressureMin: 1.1,
|
||||
distanceMax: 110,
|
||||
radiusMin: 1.1,
|
||||
radiusMax: 22,
|
||||
);
|
||||
|
||||
const Offset position = Offset.zero;
|
||||
const Duration timeStamp = Duration(days: 2);
|
||||
|
||||
test('PointerAddedEvent.copyWith()', () {
|
||||
final PointerEvent event = added.copyWith(position: position, timeStamp: timeStamp);
|
||||
const PointerEvent empty = PointerAddedEvent();
|
||||
expect(event.timeStamp, timeStamp);
|
||||
expect(event.pointer, empty.pointer);
|
||||
expect(event.kind, added.kind);
|
||||
expect(event.device, added.device);
|
||||
expect(event.position, position);
|
||||
expect(event.delta, empty.delta);
|
||||
expect(event.buttons, added.buttons);
|
||||
expect(event.down, empty.down);
|
||||
expect(event.obscured, added.obscured);
|
||||
expect(event.pressure, empty.pressure);
|
||||
expect(event.pressureMin, added.pressureMin);
|
||||
expect(event.pressureMax, added.pressureMax);
|
||||
expect(event.distance, added.distance);
|
||||
expect(event.distanceMax, added.distanceMax);
|
||||
expect(event.distanceMax, added.distanceMax);
|
||||
expect(event.size, empty.size);
|
||||
expect(event.radiusMajor, empty.radiusMajor);
|
||||
expect(event.radiusMinor, empty.radiusMinor);
|
||||
expect(event.radiusMin, added.radiusMin);
|
||||
expect(event.radiusMax, added.radiusMax);
|
||||
expect(event.orientation, added.orientation);
|
||||
expect(event.tilt, added.tilt);
|
||||
expect(event.synthesized, empty.synthesized);
|
||||
});
|
||||
|
||||
test('PointerHoverEvent.copyWith()', () {
|
||||
final PointerEvent event = hover.copyWith(position: position, timeStamp: timeStamp);
|
||||
const PointerEvent empty = PointerHoverEvent();
|
||||
expect(event.timeStamp, timeStamp);
|
||||
expect(event.pointer, empty.pointer);
|
||||
expect(event.kind, hover.kind);
|
||||
expect(event.device, hover.device);
|
||||
expect(event.position, position);
|
||||
expect(event.delta, empty.delta);
|
||||
expect(event.buttons, hover.buttons);
|
||||
expect(event.down, empty.down);
|
||||
expect(event.obscured, hover.obscured);
|
||||
expect(event.pressure, empty.pressure);
|
||||
expect(event.pressureMin, hover.pressureMin);
|
||||
expect(event.pressureMax, hover.pressureMax);
|
||||
expect(event.distance, hover.distance);
|
||||
expect(event.distanceMax, hover.distanceMax);
|
||||
expect(event.distanceMax, hover.distanceMax);
|
||||
expect(event.size, hover.size);
|
||||
expect(event.radiusMajor, hover.radiusMajor);
|
||||
expect(event.radiusMinor, hover.radiusMinor);
|
||||
expect(event.radiusMin, hover.radiusMin);
|
||||
expect(event.radiusMax, hover.radiusMax);
|
||||
expect(event.orientation, hover.orientation);
|
||||
expect(event.tilt, hover.tilt);
|
||||
expect(event.synthesized, hover.synthesized);
|
||||
});
|
||||
|
||||
test('PointerDownEvent.copyWith()', () {
|
||||
final PointerEvent event = down.copyWith(position: position, timeStamp: timeStamp);
|
||||
const PointerEvent empty = PointerDownEvent();
|
||||
expect(event.timeStamp, timeStamp);
|
||||
expect(event.pointer, down.pointer);
|
||||
expect(event.kind, down.kind);
|
||||
expect(event.device, down.device);
|
||||
expect(event.position, position);
|
||||
expect(event.delta, empty.delta);
|
||||
expect(event.buttons, down.buttons);
|
||||
expect(event.down, empty.down);
|
||||
expect(event.obscured, down.obscured);
|
||||
expect(event.pressure, empty.pressure);
|
||||
expect(event.pressureMin, down.pressureMin);
|
||||
expect(event.pressureMax, down.pressureMax);
|
||||
expect(event.distance, down.distance);
|
||||
expect(event.distanceMax, down.distanceMax);
|
||||
expect(event.distanceMax, down.distanceMax);
|
||||
expect(event.size, down.size);
|
||||
expect(event.radiusMajor, down.radiusMajor);
|
||||
expect(event.radiusMinor, down.radiusMinor);
|
||||
expect(event.radiusMin, down.radiusMin);
|
||||
expect(event.radiusMax, down.radiusMax);
|
||||
expect(event.orientation, down.orientation);
|
||||
expect(event.tilt, down.tilt);
|
||||
expect(event.synthesized, empty.synthesized);
|
||||
});
|
||||
|
||||
test('PointerMoveEvent.copyWith()', () {
|
||||
final PointerEvent event = move.copyWith(position: position, timeStamp: timeStamp);
|
||||
const PointerEvent empty = PointerMoveEvent();
|
||||
expect(event.timeStamp, timeStamp);
|
||||
expect(event.pointer, move.pointer);
|
||||
expect(event.kind, move.kind);
|
||||
expect(event.device, move.device);
|
||||
expect(event.position, position);
|
||||
expect(event.delta, move.delta);
|
||||
expect(event.buttons, move.buttons);
|
||||
expect(event.down, move.down);
|
||||
expect(event.obscured, move.obscured);
|
||||
expect(event.pressure, empty.pressure);
|
||||
expect(event.pressureMin, move.pressureMin);
|
||||
expect(event.pressureMax, move.pressureMax);
|
||||
expect(event.distance, move.distance);
|
||||
expect(event.distanceMax, move.distanceMax);
|
||||
expect(event.distanceMax, move.distanceMax);
|
||||
expect(event.size, move.size);
|
||||
expect(event.radiusMajor, move.radiusMajor);
|
||||
expect(event.radiusMinor, move.radiusMinor);
|
||||
expect(event.radiusMin, move.radiusMin);
|
||||
expect(event.radiusMax, move.radiusMax);
|
||||
expect(event.orientation, move.orientation);
|
||||
expect(event.tilt, move.tilt);
|
||||
expect(event.synthesized, move.synthesized);
|
||||
});
|
||||
|
||||
test('PointerUpEvent.copyWith()', () {
|
||||
final PointerEvent event = up.copyWith(position: position, timeStamp: timeStamp);
|
||||
const PointerEvent empty = PointerUpEvent();
|
||||
expect(event.timeStamp, timeStamp);
|
||||
expect(event.pointer, up.pointer);
|
||||
expect(event.kind, up.kind);
|
||||
expect(event.device, up.device);
|
||||
expect(event.position, position);
|
||||
expect(event.delta, up.delta);
|
||||
expect(event.buttons, up.buttons);
|
||||
expect(event.down, empty.down);
|
||||
expect(event.obscured, up.obscured);
|
||||
expect(event.pressure, empty.pressure);
|
||||
expect(event.pressureMin, up.pressureMin);
|
||||
expect(event.pressureMax, up.pressureMax);
|
||||
expect(event.distance, up.distance);
|
||||
expect(event.distanceMax, up.distanceMax);
|
||||
expect(event.distanceMax, up.distanceMax);
|
||||
expect(event.size, up.size);
|
||||
expect(event.radiusMajor, up.radiusMajor);
|
||||
expect(event.radiusMinor, up.radiusMinor);
|
||||
expect(event.radiusMin, up.radiusMin);
|
||||
expect(event.radiusMax, up.radiusMax);
|
||||
expect(event.orientation, up.orientation);
|
||||
expect(event.tilt, up.tilt);
|
||||
expect(event.synthesized, empty.synthesized);
|
||||
});
|
||||
|
||||
test('PointerRemovedEvent.copyWith()', () {
|
||||
final PointerEvent event = removed.copyWith(position: position, timeStamp: timeStamp);
|
||||
const PointerEvent empty = PointerRemovedEvent();
|
||||
expect(event.timeStamp, timeStamp);
|
||||
expect(event.pointer, empty.pointer);
|
||||
expect(event.kind, removed.kind);
|
||||
expect(event.device, removed.device);
|
||||
expect(event.position, position);
|
||||
expect(event.delta, empty.delta);
|
||||
expect(event.buttons, removed.buttons);
|
||||
expect(event.down, empty.down);
|
||||
expect(event.obscured, removed.obscured);
|
||||
expect(event.pressure, empty.pressure);
|
||||
expect(event.pressureMin, removed.pressureMin);
|
||||
expect(event.pressureMax, removed.pressureMax);
|
||||
expect(event.distance, empty.distance);
|
||||
expect(event.distanceMax, removed.distanceMax);
|
||||
expect(event.distanceMax, removed.distanceMax);
|
||||
expect(event.size, empty.size);
|
||||
expect(event.radiusMajor, empty.radiusMajor);
|
||||
expect(event.radiusMinor, empty.radiusMinor);
|
||||
expect(event.radiusMin, removed.radiusMin);
|
||||
expect(event.radiusMax, removed.radiusMax);
|
||||
expect(event.orientation, empty.orientation);
|
||||
expect(event.tilt, empty.tilt);
|
||||
expect(event.synthesized, empty.synthesized);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _expectTransformedEvent({
|
||||
|
||||
@ -8,13 +8,16 @@ import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import '../flutter_test_alternative.dart';
|
||||
|
||||
typedef HandleEventCallback = void Function(PointerEvent event);
|
||||
|
||||
class TestGestureFlutterBinding extends BindingBase with GestureBinding {
|
||||
class TestGestureFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding {
|
||||
HandleEventCallback callback;
|
||||
FrameCallback frameCallback;
|
||||
Duration frameTime;
|
||||
|
||||
@override
|
||||
void handleEvent(PointerEvent event, HitTestEntry entry) {
|
||||
@ -22,6 +25,18 @@ class TestGestureFlutterBinding extends BindingBase with GestureBinding {
|
||||
if (callback != null)
|
||||
callback(event);
|
||||
}
|
||||
|
||||
@override
|
||||
Duration get currentSystemFrameTimeStamp {
|
||||
assert(frameTime != null);
|
||||
return frameTime;
|
||||
}
|
||||
|
||||
@override
|
||||
int scheduleFrameCallback(FrameCallback callback, {bool rescheduling = false}) {
|
||||
frameCallback = callback;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
TestGestureFlutterBinding _binding = TestGestureFlutterBinding();
|
||||
@ -29,6 +44,7 @@ TestGestureFlutterBinding _binding = TestGestureFlutterBinding();
|
||||
void ensureTestGestureBinding() {
|
||||
_binding ??= TestGestureFlutterBinding();
|
||||
assert(GestureBinding.instance != null);
|
||||
assert(SchedulerBinding.instance != null);
|
||||
}
|
||||
|
||||
void main() {
|
||||
@ -299,4 +315,107 @@ void main() {
|
||||
expect(events[4].buttons, equals(0));
|
||||
}
|
||||
});
|
||||
|
||||
test('Pointer event resampling', () {
|
||||
const ui.PointerDataPacket packet = ui.PointerDataPacket(
|
||||
data: <ui.PointerData>[
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.add,
|
||||
physicalX: 0.0,
|
||||
timeStamp: Duration(milliseconds: 0),
|
||||
),
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.down,
|
||||
physicalX: 0.0,
|
||||
timeStamp: Duration(milliseconds: 1),
|
||||
),
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.move,
|
||||
physicalX: 10.0,
|
||||
timeStamp: Duration(milliseconds: 2),
|
||||
),
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.move,
|
||||
physicalX: 20.0,
|
||||
timeStamp: Duration(milliseconds: 3),
|
||||
),
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.move,
|
||||
physicalX: 30.0,
|
||||
timeStamp: Duration(milliseconds: 4),
|
||||
),
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.up,
|
||||
physicalX: 40.0,
|
||||
timeStamp: Duration(milliseconds: 5),
|
||||
),
|
||||
ui.PointerData(
|
||||
change: ui.PointerChange.remove,
|
||||
physicalX: 40.0,
|
||||
timeStamp: Duration(milliseconds: 6),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final List<PointerEvent> pointerRouterEvents = <PointerEvent>[];
|
||||
GestureBinding.instance.pointerRouter.addGlobalRoute(pointerRouterEvents.add);
|
||||
|
||||
final List<PointerEvent> events = <PointerEvent>[];
|
||||
_binding.callback = events.add;
|
||||
|
||||
GestureBinding.instance.resamplingEnabled = true;
|
||||
GestureBinding.instance.samplingOffset = const Duration(microseconds: -5500);
|
||||
|
||||
ui.window.onPointerDataPacket(packet);
|
||||
|
||||
// No pointer events should have been dispatched yet.
|
||||
expect(events.length, 0);
|
||||
|
||||
// Frame callback should have been requested.
|
||||
FrameCallback callback = _binding.frameCallback;
|
||||
_binding.frameCallback = null;
|
||||
expect(callback, isNotNull);
|
||||
|
||||
_binding.frameTime = const Duration(milliseconds: 7);
|
||||
callback(Duration.zero);
|
||||
|
||||
// One pointer event should have been dispatched.
|
||||
expect(events.length, 1);
|
||||
expect(events[0].runtimeType, equals(PointerDownEvent));
|
||||
expect(events[0].timeStamp, _binding.frameTime + GestureBinding.instance.samplingOffset);
|
||||
expect(events[0].position, Offset(5.0 / ui.window.devicePixelRatio, 0.0));
|
||||
|
||||
// Second frame callback should have been requested.
|
||||
callback = _binding.frameCallback;
|
||||
_binding.frameCallback = null;
|
||||
expect(callback, isNotNull);
|
||||
|
||||
_binding.frameTime = const Duration(milliseconds: 9);
|
||||
callback(Duration.zero);
|
||||
|
||||
// Second pointer event should have been dispatched.
|
||||
expect(events.length, 2);
|
||||
expect(events[1].timeStamp, _binding.frameTime + GestureBinding.instance.samplingOffset);
|
||||
expect(events[1].runtimeType, equals(PointerMoveEvent));
|
||||
expect(events[1].position, Offset(25.0 / ui.window.devicePixelRatio, 0.0));
|
||||
expect(events[1].delta, Offset(20.0 / ui.window.devicePixelRatio, 0.0));
|
||||
|
||||
// Third frame callback should have been requested.
|
||||
callback = _binding.frameCallback;
|
||||
_binding.frameCallback = null;
|
||||
expect(callback, isNotNull);
|
||||
|
||||
_binding.frameTime = const Duration(milliseconds: 11);
|
||||
callback(Duration.zero);
|
||||
|
||||
// Third pointer event should have been dispatched.
|
||||
expect(events.length, 3);
|
||||
expect(events[2].timeStamp, _binding.frameTime + GestureBinding.instance.samplingOffset);
|
||||
expect(events[2].runtimeType, equals(PointerUpEvent));
|
||||
expect(events[2].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
|
||||
|
||||
// No frame callback should have been requested.
|
||||
callback = _binding.frameCallback;
|
||||
expect(callback, isNull);
|
||||
});
|
||||
}
|
||||
|
||||
605
packages/flutter/test/gestures/resampler_test.dart
Normal file
605
packages/flutter/test/gestures/resampler_test.dart
Normal file
@ -0,0 +1,605 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
import '../flutter_test_alternative.dart';
|
||||
|
||||
void main() {
|
||||
PointerEvent _createSimulatedPointerAddedEvent(
|
||||
int timeStampUs,
|
||||
double x,
|
||||
double y,
|
||||
) {
|
||||
return PointerAddedEvent(
|
||||
timeStamp: Duration(microseconds: timeStampUs),
|
||||
position: Offset(x, y),
|
||||
);
|
||||
}
|
||||
|
||||
PointerEvent _createSimulatedPointerRemovedEvent(
|
||||
int timeStampUs,
|
||||
double x,
|
||||
double y,
|
||||
) {
|
||||
return PointerRemovedEvent(
|
||||
timeStamp: Duration(microseconds: timeStampUs),
|
||||
position: Offset(x, y),
|
||||
);
|
||||
}
|
||||
|
||||
PointerEvent _createSimulatedPointerDownEvent(
|
||||
int timeStampUs,
|
||||
double x,
|
||||
double y,
|
||||
) {
|
||||
return PointerDownEvent(
|
||||
timeStamp: Duration(microseconds: timeStampUs),
|
||||
position: Offset(x, y),
|
||||
);
|
||||
}
|
||||
|
||||
PointerEvent _createSimulatedPointerMoveEvent(
|
||||
int timeStampUs,
|
||||
double x,
|
||||
double y,
|
||||
double deltaX,
|
||||
double deltaY,
|
||||
) {
|
||||
return PointerMoveEvent(
|
||||
timeStamp: Duration(microseconds: timeStampUs),
|
||||
position: Offset(x, y),
|
||||
delta: Offset(deltaX, deltaY),
|
||||
);
|
||||
}
|
||||
|
||||
PointerEvent _createSimulatedPointerUpEvent(
|
||||
int timeStampUs,
|
||||
double x,
|
||||
double y,
|
||||
) {
|
||||
return PointerUpEvent(
|
||||
timeStamp: Duration(microseconds: timeStampUs),
|
||||
position: Offset(x, y),
|
||||
);
|
||||
}
|
||||
|
||||
test('basic', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 50.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(2000, 10.0, 40.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerMoveEvent(3000, 20.0, 30.0, 10.0, -10.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerMoveEvent(4000, 30.0, 20.0, 10.0, -10.0);
|
||||
final PointerEvent event4 = _createSimulatedPointerUpEvent(5000, 40.0, 10.0);
|
||||
final PointerEvent event5 = _createSimulatedPointerRemovedEvent(6000, 50.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3)
|
||||
..addEvent(event4)
|
||||
..addEvent(event5);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 500), result.add);
|
||||
|
||||
// No pointer event should have been returned yet.
|
||||
expect(result.isEmpty, true);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// Add pointer event should have been returned.
|
||||
expect(result.length, 1);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 5.0);
|
||||
expect(result[0].position.dy, 45.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 2500), result.add);
|
||||
|
||||
// Down pointer event should have been returned.
|
||||
expect(result.length, 2);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 2500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 15.0);
|
||||
expect(result[1].position.dy, 35.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 3500), result.add);
|
||||
|
||||
// Move pointer event should have been returned.
|
||||
expect(result.length, 3);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 3500));
|
||||
expect(result[2] is PointerMoveEvent, true);
|
||||
expect(result[2].position.dx, 25.0);
|
||||
expect(result[2].position.dy, 25.0);
|
||||
expect(result[2].delta.dx, 10.0);
|
||||
expect(result[2].delta.dy, -10.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 4500), result.add);
|
||||
|
||||
// Up pointer event should have been returned.
|
||||
expect(result.length, 4);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 4500));
|
||||
expect(result[3] is PointerUpEvent, true);
|
||||
expect(result[3].position.dx, 35.0);
|
||||
expect(result[3].position.dy, 15.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 5500), result.add);
|
||||
|
||||
// Remove pointer event should have been returned.
|
||||
expect(result.length, 5);
|
||||
expect(result[4].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[4] is PointerRemovedEvent, true);
|
||||
expect(result[4].position.dx, 45.0);
|
||||
expect(result[4].position.dy, 5.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 6500), result.add);
|
||||
|
||||
// No pointer event should have been returned.
|
||||
expect(result.length, 5);
|
||||
});
|
||||
|
||||
test('stream', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 50.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(2000, 10.0, 40.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerMoveEvent(3000, 20.0, 30.0, 10.0, -10.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerMoveEvent(4000, 30.0, 20.0, 10.0, -10.0);
|
||||
final PointerEvent event4 = _createSimulatedPointerUpEvent(5000, 40.0, 10.0);
|
||||
final PointerEvent event5 = _createSimulatedPointerRemovedEvent(6000, 50.0, 0.0);
|
||||
|
||||
resampler.addEvent(event0);
|
||||
|
||||
//
|
||||
// Initial sample time a 0.5 ms.
|
||||
//
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 500), result.add);
|
||||
|
||||
// No pointer event should have been returned yet.
|
||||
expect(result.isEmpty, true);
|
||||
|
||||
resampler.addEvent(event1);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 500), result.add);
|
||||
|
||||
// No pointer event should have been returned yet.
|
||||
expect(result.isEmpty, true);
|
||||
|
||||
//
|
||||
// Advance sample time to 1.5 ms.
|
||||
//
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// Add pointer event should have been returned.
|
||||
expect(result.length, 1);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 5.0);
|
||||
expect(result[0].position.dy, 45.0);
|
||||
|
||||
resampler.addEvent(event2);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// No more pointer events should have been returned.
|
||||
expect(result.length, 1);
|
||||
|
||||
//
|
||||
// Advance sample time to 2.5 ms.
|
||||
//
|
||||
|
||||
resampler.sample(const Duration(microseconds: 2500), result.add);
|
||||
|
||||
// Down pointer event should have been returned.
|
||||
expect(result.length, 2);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 2500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 15.0);
|
||||
expect(result[1].position.dy, 35.0);
|
||||
|
||||
resampler.addEvent(event3);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 2500), result.add);
|
||||
|
||||
// No more pointer events should have been returned.
|
||||
expect(result.length, 2);
|
||||
|
||||
//
|
||||
// Advance sample time to 3.5 ms.
|
||||
//
|
||||
|
||||
resampler.sample(const Duration(microseconds: 3500), result.add);
|
||||
|
||||
// Move pointer event should have been returned.
|
||||
expect(result.length, 3);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 3500));
|
||||
expect(result[2] is PointerMoveEvent, true);
|
||||
expect(result[2].position.dx, 25.0);
|
||||
expect(result[2].position.dy, 25.0);
|
||||
expect(result[2].delta.dx, 10.0);
|
||||
expect(result[2].delta.dy, -10.0);
|
||||
|
||||
resampler.addEvent(event4);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 3500), result.add);
|
||||
|
||||
// No more pointer events should have been returned.
|
||||
expect(result.length, 3);
|
||||
|
||||
//
|
||||
// Advance sample time to 4.5 ms.
|
||||
//
|
||||
|
||||
resampler.sample(const Duration(microseconds: 4500), result.add);
|
||||
|
||||
// Up pointer event should have been returned.
|
||||
expect(result.length, 4);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 4500));
|
||||
expect(result[3] is PointerUpEvent, true);
|
||||
expect(result[3].position.dx, 35.0);
|
||||
expect(result[3].position.dy, 15.0);
|
||||
|
||||
resampler.addEvent(event5);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 4500), result.add);
|
||||
|
||||
// No more pointer events should have been returned.
|
||||
expect(result.length, 4);
|
||||
|
||||
//
|
||||
// Advance sample time to 5.5 ms.
|
||||
//
|
||||
|
||||
resampler.sample(const Duration(microseconds: 5500), result.add);
|
||||
|
||||
// Remove pointer event should have been returned.
|
||||
expect(result.length, 5);
|
||||
expect(result[4].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[4] is PointerRemovedEvent, true);
|
||||
expect(result[4].position.dx, 45.0);
|
||||
expect(result[4].position.dy, 5.0);
|
||||
|
||||
//
|
||||
// Advance sample time to 6.5 ms.
|
||||
//
|
||||
|
||||
resampler.sample(const Duration(microseconds: 6500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.length, 5);
|
||||
});
|
||||
|
||||
test('quick tap', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerUpEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerRemovedEvent(1000, 0.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// All pointer events should have been returned.
|
||||
expect(result.length, 4);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 0.0);
|
||||
expect(result[0].position.dy, 0.0);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 0.0);
|
||||
expect(result[1].position.dy, 0.0);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[2] is PointerUpEvent, true);
|
||||
expect(result[2].position.dx, 0.0);
|
||||
expect(result[2].position.dy, 0.0);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[3] is PointerRemovedEvent, true);
|
||||
expect(result[3].position.dx, 0.0);
|
||||
expect(result[3].position.dy, 0.0);
|
||||
});
|
||||
|
||||
test('advance slowly', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerMoveEvent(2000, 10.0, 0.0, 10.0, 0.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerUpEvent(3000, 20.0, 0.0);
|
||||
final PointerEvent event4 = _createSimulatedPointerRemovedEvent(3000, 20.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3)
|
||||
..addEvent(event4);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// Added and down pointer events should have been returned.
|
||||
expect(result.length, 2);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 5.0);
|
||||
expect(result[0].position.dy, 0.0);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 5.0);
|
||||
expect(result[1].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.length, 2);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1750), result.add);
|
||||
|
||||
// Move pointer event should have been returned.
|
||||
expect(result.length, 3);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 1750));
|
||||
expect(result[2] is PointerMoveEvent, true);
|
||||
expect(result[2].position.dx, 7.5);
|
||||
expect(result[2].position.dy, 0.0);
|
||||
expect(result[2].delta.dx, 2.5);
|
||||
expect(result[2].delta.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 2000), result.add);
|
||||
|
||||
// Another move pointer event should have been returned.
|
||||
expect(result.length, 4);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 2000));
|
||||
expect(result[3] is PointerMoveEvent, true);
|
||||
expect(result[3].position.dx, 10.0);
|
||||
expect(result[3].position.dy, 0.0);
|
||||
expect(result[3].delta.dx, 2.5);
|
||||
expect(result[3].delta.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 2500), result.add);
|
||||
|
||||
// Last two pointer events should have been returned.
|
||||
expect(result.length, 6);
|
||||
expect(result[4].timeStamp, const Duration(microseconds: 2500));
|
||||
expect(result[4] is PointerUpEvent, true);
|
||||
expect(result[4].position.dx, 15.0);
|
||||
expect(result[4].position.dy, 0.0);
|
||||
expect(result[5].timeStamp, const Duration(microseconds: 2500));
|
||||
expect(result[5] is PointerRemovedEvent, true);
|
||||
expect(result[5].position.dx, 15.0);
|
||||
expect(result[5].position.dy, 0.0);
|
||||
});
|
||||
|
||||
test('advance fast', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerMoveEvent(2000, 5.0, 0.0, 5.0, 0.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerMoveEvent(3000, 20.0, 0.0, 15.0, 0.0);
|
||||
final PointerEvent event4 = _createSimulatedPointerUpEvent(4000, 30.0, 0.0);
|
||||
final PointerEvent event5 = _createSimulatedPointerRemovedEvent(4000, 30.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3)
|
||||
..addEvent(event4)
|
||||
..addEvent(event5);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 2500), result.add);
|
||||
|
||||
// Add and down pointer events should have been returned.
|
||||
expect(result.length, 2);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 2500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 12.5);
|
||||
expect(result[0].position.dy, 0.0);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 2500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 12.5);
|
||||
expect(result[1].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 5500), result.add);
|
||||
|
||||
// Up and removed pointer events should have been returned.
|
||||
expect(result.length, 4);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[2] is PointerUpEvent, true);
|
||||
expect(result[2].position.dx, 30.0);
|
||||
expect(result[2].position.dy, 0.0);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[3] is PointerRemovedEvent, true);
|
||||
expect(result[3].position.dx, 30.0);
|
||||
expect(result[3].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 6500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.length, 4);
|
||||
});
|
||||
|
||||
test('skip', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerMoveEvent(2000, 10.0, 0.0, 10.0, 0.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerUpEvent(3000, 10.0, 0.0);
|
||||
final PointerEvent event4 = _createSimulatedPointerDownEvent(4000, 20.0, 0.0);
|
||||
final PointerEvent event5 = _createSimulatedPointerUpEvent(5000, 30.0, 0.0);
|
||||
final PointerEvent event6 = _createSimulatedPointerRemovedEvent(5000, 30.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3)
|
||||
..addEvent(event4)
|
||||
..addEvent(event5)
|
||||
..addEvent(event6);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 1500), result.add);
|
||||
|
||||
// Added and down pointer events should have been returned.
|
||||
expect(result.length, 2);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 5.0);
|
||||
expect(result[0].position.dy, 0.0);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 1500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 5.0);
|
||||
expect(result[1].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 4500), result.add);
|
||||
|
||||
// All remaining pointer events should have been returned.
|
||||
expect(result.length, 6);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 4500));
|
||||
expect(result[2] is PointerUpEvent, true);
|
||||
expect(result[2].position.dx, 25.0);
|
||||
expect(result[2].position.dy, 0.0);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 4500));
|
||||
expect(result[3] is PointerDownEvent, true);
|
||||
expect(result[3].position.dx, 25.0);
|
||||
expect(result[3].position.dy, 0.0);
|
||||
expect(result[4].timeStamp, const Duration(microseconds: 4500));
|
||||
expect(result[4] is PointerUpEvent, true);
|
||||
expect(result[4].position.dx, 25.0);
|
||||
expect(result[4].position.dy, 0.0);
|
||||
expect(result[5].timeStamp, const Duration(microseconds: 4500));
|
||||
expect(result[5] is PointerRemovedEvent, true);
|
||||
expect(result[5].position.dx, 25.0);
|
||||
expect(result[5].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 5500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.length, 6);
|
||||
});
|
||||
|
||||
test('skip all', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerUpEvent(4000, 30.0, 0.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerRemovedEvent(4000, 30.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.isEmpty, true);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 5500), result.add);
|
||||
|
||||
// All remaining pointer events should have been returned.
|
||||
expect(result.length, 4);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 30.0);
|
||||
expect(result[0].position.dy, 0.0);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 30.0);
|
||||
expect(result[1].position.dy, 0.0);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[2] is PointerUpEvent, true);
|
||||
expect(result[2].position.dx, 30.0);
|
||||
expect(result[2].position.dy, 0.0);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 5500));
|
||||
expect(result[3] is PointerRemovedEvent, true);
|
||||
expect(result[3].position.dx, 30.0);
|
||||
expect(result[3].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 6500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.length, 4);
|
||||
});
|
||||
|
||||
test('stop', () {
|
||||
final PointerEventResampler resampler = PointerEventResampler();
|
||||
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
|
||||
final PointerEvent event1 = _createSimulatedPointerDownEvent(2000, 0.0, 0.0);
|
||||
final PointerEvent event2 = _createSimulatedPointerMoveEvent(3000, 10.0, 0.0, 10.0, 0.0);
|
||||
final PointerEvent event3 = _createSimulatedPointerUpEvent(4000, 20.0, 0.0);
|
||||
final PointerEvent event4 = _createSimulatedPointerRemovedEvent(5000, 20.0, 0.0);
|
||||
|
||||
resampler
|
||||
..addEvent(event0)
|
||||
..addEvent(event1)
|
||||
..addEvent(event2)
|
||||
..addEvent(event3)
|
||||
..addEvent(event4);
|
||||
|
||||
final List<PointerEvent> result = <PointerEvent>[];
|
||||
|
||||
resampler.sample(const Duration(microseconds: 500), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.isEmpty, true);
|
||||
|
||||
resampler.stop(result.add);
|
||||
|
||||
// All pointer events should have been returned with orignal
|
||||
// time stamps and positions.
|
||||
expect(result.length, 5);
|
||||
expect(result[0].timeStamp, const Duration(microseconds: 1000));
|
||||
expect(result[0] is PointerAddedEvent, true);
|
||||
expect(result[0].position.dx, 0.0);
|
||||
expect(result[0].position.dy, 0.0);
|
||||
expect(result[1].timeStamp, const Duration(microseconds: 2000));
|
||||
expect(result[1] is PointerDownEvent, true);
|
||||
expect(result[1].position.dx, 0.0);
|
||||
expect(result[1].position.dy, 0.0);
|
||||
expect(result[2].timeStamp, const Duration(microseconds: 3000));
|
||||
expect(result[2] is PointerMoveEvent, true);
|
||||
expect(result[2].position.dx, 10.0);
|
||||
expect(result[2].position.dy, 0.0);
|
||||
expect(result[2].delta.dx, 10.0);
|
||||
expect(result[2].delta.dy, 0.0);
|
||||
expect(result[3].timeStamp, const Duration(microseconds: 4000));
|
||||
expect(result[3] is PointerUpEvent, true);
|
||||
expect(result[3].position.dx, 20.0);
|
||||
expect(result[3].position.dy, 0.0);
|
||||
expect(result[4].timeStamp, const Duration(microseconds: 5000));
|
||||
expect(result[4] is PointerRemovedEvent, true);
|
||||
expect(result[4].position.dx, 20.0);
|
||||
expect(result[4].position.dy, 0.0);
|
||||
|
||||
resampler.sample(const Duration(microseconds: 10000), result.add);
|
||||
|
||||
// No pointer events should have been returned.
|
||||
expect(result.length, 5);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user