Transform PointerEvents to the local coordinate system of the event receiver (#32192)
This commit is contained in:
parent
a9518da068
commit
ae23d4a490
@ -195,7 +195,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
}
|
}
|
||||||
for (HitTestEntry entry in hitTestResult.path) {
|
for (HitTestEntry entry in hitTestResult.path) {
|
||||||
try {
|
try {
|
||||||
entry.target.handleEvent(event, entry);
|
entry.target.handleEvent(event.transformed(entry.transform), entry);
|
||||||
} catch (exception, stack) {
|
} catch (exception, stack) {
|
||||||
FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
|
FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
|
||||||
exception: exception,
|
exception: exception,
|
||||||
|
@ -20,14 +20,28 @@ class DragDownDetails {
|
|||||||
/// Creates details for a [GestureDragDownCallback].
|
/// Creates details for a [GestureDragDownCallback].
|
||||||
///
|
///
|
||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
DragDownDetails({ this.globalPosition = Offset.zero })
|
DragDownDetails({
|
||||||
: assert(globalPosition != null);
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
|
}) : assert(globalPosition != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// The global position at which the pointer contacted the screen.
|
/// The global position at which the pointer contacted the screen.
|
||||||
///
|
///
|
||||||
/// Defaults to the origin if not specified in the constructor.
|
/// Defaults to the origin if not specified in the constructor.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [localPosition], which is the [globalPosition] transformed to the
|
||||||
|
/// coordinate space of the event receiver.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position in the coordinate system of the event receiver at
|
||||||
|
/// which the pointer contacted the screen.
|
||||||
|
///
|
||||||
|
/// Defaults to [globalPosition] if not specified in the constructor.
|
||||||
|
final Offset localPosition;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$runtimeType($globalPosition)';
|
String toString() => '$runtimeType($globalPosition)';
|
||||||
}
|
}
|
||||||
@ -52,8 +66,12 @@ class DragStartDetails {
|
|||||||
/// Creates details for a [GestureDragStartCallback].
|
/// Creates details for a [GestureDragStartCallback].
|
||||||
///
|
///
|
||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
DragStartDetails({ this.sourceTimeStamp, this.globalPosition = Offset.zero })
|
DragStartDetails({
|
||||||
: assert(globalPosition != null);
|
this.sourceTimeStamp,
|
||||||
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
|
}) : assert(globalPosition != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// Recorded timestamp of the source pointer event that triggered the drag
|
/// Recorded timestamp of the source pointer event that triggered the drag
|
||||||
/// event.
|
/// event.
|
||||||
@ -64,8 +82,19 @@ class DragStartDetails {
|
|||||||
/// The global position at which the pointer contacted the screen.
|
/// The global position at which the pointer contacted the screen.
|
||||||
///
|
///
|
||||||
/// Defaults to the origin if not specified in the constructor.
|
/// Defaults to the origin if not specified in the constructor.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [localPosition], which is the [globalPosition] transformed to the
|
||||||
|
/// coordinate space of the event receiver.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position in the coordinate system of the event receiver at
|
||||||
|
/// which the pointer contacted the screen.
|
||||||
|
///
|
||||||
|
/// Defaults to [globalPosition] if not specified in the constructor.
|
||||||
|
final Offset localPosition;
|
||||||
|
|
||||||
// TODO(ianh): Expose the current position, so that you can have a no-jump
|
// TODO(ianh): Expose the current position, so that you can have a no-jump
|
||||||
// drag even when disambiguating (though of course it would lag the finger
|
// drag even when disambiguating (though of course it would lag the finger
|
||||||
// instead).
|
// instead).
|
||||||
@ -104,10 +133,12 @@ class DragUpdateDetails {
|
|||||||
this.delta = Offset.zero,
|
this.delta = Offset.zero,
|
||||||
this.primaryDelta,
|
this.primaryDelta,
|
||||||
@required this.globalPosition,
|
@required this.globalPosition,
|
||||||
|
Offset localPosition,
|
||||||
}) : assert(delta != null),
|
}) : assert(delta != null),
|
||||||
assert(primaryDelta == null
|
assert(primaryDelta == null
|
||||||
|| (primaryDelta == delta.dx && delta.dy == 0.0)
|
|| (primaryDelta == delta.dx && delta.dy == 0.0)
|
||||||
|| (primaryDelta == delta.dy && delta.dx == 0.0));
|
|| (primaryDelta == delta.dy && delta.dx == 0.0)),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// Recorded timestamp of the source pointer event that triggered the drag
|
/// Recorded timestamp of the source pointer event that triggered the drag
|
||||||
/// event.
|
/// event.
|
||||||
@ -115,7 +146,8 @@ class DragUpdateDetails {
|
|||||||
/// Could be null if triggered from proxied events such as accessibility.
|
/// Could be null if triggered from proxied events such as accessibility.
|
||||||
final Duration sourceTimeStamp;
|
final Duration sourceTimeStamp;
|
||||||
|
|
||||||
/// The amount the pointer has moved since the previous update.
|
/// The amount the pointer has moved in the coordinate space of the event
|
||||||
|
/// receiver since the previous update.
|
||||||
///
|
///
|
||||||
/// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
|
/// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
|
||||||
/// a horizontal or vertical drag), then this offset contains only the delta
|
/// a horizontal or vertical drag), then this offset contains only the delta
|
||||||
@ -124,7 +156,8 @@ class DragUpdateDetails {
|
|||||||
/// Defaults to zero if not specified in the constructor.
|
/// Defaults to zero if not specified in the constructor.
|
||||||
final Offset delta;
|
final Offset delta;
|
||||||
|
|
||||||
/// The amount the pointer has moved along the primary axis since the previous
|
/// The amount the pointer has moved along the primary axis in the coordinate
|
||||||
|
/// space of the event receiver since the previous
|
||||||
/// update.
|
/// update.
|
||||||
///
|
///
|
||||||
/// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
|
/// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
|
||||||
@ -137,8 +170,19 @@ class DragUpdateDetails {
|
|||||||
final double primaryDelta;
|
final double primaryDelta;
|
||||||
|
|
||||||
/// The pointer's global position when it triggered this update.
|
/// The pointer's global position when it triggered this update.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [localPosition], which is the [globalPosition] transformed to the
|
||||||
|
/// coordinate space of the event receiver.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position in the coordinate system of the event receiver at
|
||||||
|
/// which the pointer contacted the screen.
|
||||||
|
///
|
||||||
|
/// Defaults to [globalPosition] if not specified in the constructor.
|
||||||
|
final Offset localPosition;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$runtimeType($delta)';
|
String toString() => '$runtimeType($delta)';
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class EagerGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerDownEvent event) {
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
// We call startTrackingPointer as this is where OneSequenceGestureRecognizer joins the arena.
|
// We call startTrackingPointer as this is where OneSequenceGestureRecognizer joins the arena.
|
||||||
startTrackingPointer(event.pointer);
|
startTrackingPointer(event.pointer, event.transform);
|
||||||
resolve(GestureDisposition.accepted);
|
resolve(GestureDisposition.accepted);
|
||||||
stopTrackingPointer(event.pointer);
|
stopTrackingPointer(event.pointer);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'dart:ui' show Offset, PointerDeviceKind;
|
import 'dart:ui' show Offset, PointerDeviceKind;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
export 'dart:ui' show Offset, PointerDeviceKind;
|
export 'dart:ui' show Offset, PointerDeviceKind;
|
||||||
|
|
||||||
@ -202,7 +203,9 @@ abstract class PointerEvent extends Diagnosticable {
|
|||||||
this.kind = PointerDeviceKind.touch,
|
this.kind = PointerDeviceKind.touch,
|
||||||
this.device = 0,
|
this.device = 0,
|
||||||
this.position = Offset.zero,
|
this.position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
this.delta = Offset.zero,
|
this.delta = Offset.zero,
|
||||||
|
Offset localDelta,
|
||||||
this.buttons = 0,
|
this.buttons = 0,
|
||||||
this.down = false,
|
this.down = false,
|
||||||
this.obscured = false,
|
this.obscured = false,
|
||||||
@ -220,7 +223,11 @@ abstract class PointerEvent extends Diagnosticable {
|
|||||||
this.tilt = 0.0,
|
this.tilt = 0.0,
|
||||||
this.platformData = 0,
|
this.platformData = 0,
|
||||||
this.synthesized = false,
|
this.synthesized = false,
|
||||||
});
|
this.transform,
|
||||||
|
this.original,
|
||||||
|
}) : localPosition = localPosition ?? position,
|
||||||
|
localDelta = localDelta ?? delta;
|
||||||
|
|
||||||
|
|
||||||
/// Time of event dispatch, relative to an arbitrary timeline.
|
/// Time of event dispatch, relative to an arbitrary timeline.
|
||||||
final Duration timeStamp;
|
final Duration timeStamp;
|
||||||
@ -237,14 +244,46 @@ abstract class PointerEvent extends Diagnosticable {
|
|||||||
|
|
||||||
/// Coordinate of the position of the pointer, in logical pixels in the global
|
/// Coordinate of the position of the pointer, in logical pixels in the global
|
||||||
/// coordinate space.
|
/// coordinate space.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [localPosition], which is the [position] transformed into the local
|
||||||
|
/// coordinate system of the event receiver.
|
||||||
final Offset position;
|
final Offset position;
|
||||||
|
|
||||||
|
/// The [position] transformed into the event receiver's local coordinate
|
||||||
|
/// system according to [transform].
|
||||||
|
///
|
||||||
|
/// If this event has not been transformed, [position] is returned as-is.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [globalPosition], which is the position in the global coordinate
|
||||||
|
/// system of the screen.
|
||||||
|
final Offset localPosition;
|
||||||
|
|
||||||
/// Distance in logical pixels that the pointer moved since the last
|
/// Distance in logical pixels that the pointer moved since the last
|
||||||
/// [PointerMoveEvent] or [PointerHoverEvent].
|
/// [PointerMoveEvent] or [PointerHoverEvent].
|
||||||
///
|
///
|
||||||
/// This value is always 0.0 for down, up, and cancel events.
|
/// This value is always 0.0 for down, up, and cancel events.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [localDelta], which is the [delta] transformed into the local
|
||||||
|
/// coordinate space of the event receiver.
|
||||||
final Offset delta;
|
final Offset delta;
|
||||||
|
|
||||||
|
/// The [delta] transformed into the event receiver's local coordinate
|
||||||
|
/// system according to [transform].
|
||||||
|
///
|
||||||
|
/// If this event has not been transformed, [delta] is returned as-is.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [delta], which is the distance the pointer moved in the global
|
||||||
|
/// coordinate system of the screen.
|
||||||
|
final Offset localDelta;
|
||||||
|
|
||||||
/// Bit field using the *Button constants such as [kPrimaryMouseButton],
|
/// Bit field using the *Button constants such as [kPrimaryMouseButton],
|
||||||
/// [kSecondaryStylusButton], etc.
|
/// [kSecondaryStylusButton], etc.
|
||||||
///
|
///
|
||||||
@ -390,11 +429,56 @@ abstract class PointerEvent extends Diagnosticable {
|
|||||||
/// the difference between the 2 events in that case.
|
/// the difference between the 2 events in that case.
|
||||||
final bool synthesized;
|
final bool synthesized;
|
||||||
|
|
||||||
|
/// The transformation used to transform this event from the global coordinate
|
||||||
|
/// space into the coordinate space of the event receiver.
|
||||||
|
///
|
||||||
|
/// This value affects what is returned by [localPosition] and [localDelta].
|
||||||
|
/// If this value is null, it is treated as the identity transformation.
|
||||||
|
///
|
||||||
|
/// Unlike a paint transform, this transform usually does not contain any
|
||||||
|
/// "perspective" components, meaning that the third row and the third column
|
||||||
|
/// of the matrix should be equal to "0, 0, 1, 0". This ensures that
|
||||||
|
/// [localPosition] describes the point in the local coordinate system of the
|
||||||
|
/// event receiver at which the user is actually touching the screen.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [transformed], which transforms this event into a different coordinate
|
||||||
|
/// space.
|
||||||
|
final Matrix4 transform;
|
||||||
|
|
||||||
|
/// The original un-transformed [PointerEvent] before any [transform]s were
|
||||||
|
/// applied.
|
||||||
|
///
|
||||||
|
/// If [transform] is null or the identity transformation this may be null.
|
||||||
|
///
|
||||||
|
/// When multiple event receivers in different coordinate spaces receive an
|
||||||
|
/// event, they all receive the event transformed to their local coordinate
|
||||||
|
/// space. The [original] property can be used to determine if all those
|
||||||
|
/// transformed events actually originated from the same pointer interaction.
|
||||||
|
final PointerEvent original;
|
||||||
|
|
||||||
|
/// Transforms the event from the global coordinate space into the coordinate
|
||||||
|
/// space of an event receiver.
|
||||||
|
///
|
||||||
|
/// The coordinate space of the event receiver is described by `transform`. A
|
||||||
|
/// null value for `transform` is treated as the identity transformation.
|
||||||
|
///
|
||||||
|
/// The method may return the same object instance if for example the
|
||||||
|
/// transformation has no effect on the event.
|
||||||
|
///
|
||||||
|
/// Transforms are not commutative. If this method is called on a
|
||||||
|
/// [PointerEvent] that has a non-null [transform] value, that value will be
|
||||||
|
/// overridden by the provided `transform`.
|
||||||
|
PointerEvent transformed(Matrix4 transform);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(DiagnosticsProperty<Offset>('position', position));
|
properties.add(DiagnosticsProperty<Offset>('position', position));
|
||||||
|
properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition, defaultValue: position, level: DiagnosticLevel.debug));
|
||||||
properties.add(DiagnosticsProperty<Offset>('delta', delta, defaultValue: Offset.zero, level: DiagnosticLevel.debug));
|
properties.add(DiagnosticsProperty<Offset>('delta', delta, defaultValue: Offset.zero, level: DiagnosticLevel.debug));
|
||||||
|
properties.add(DiagnosticsProperty<Offset>('localDelta', localDelta, defaultValue: delta, level: DiagnosticLevel.debug));
|
||||||
properties.add(DiagnosticsProperty<Duration>('timeStamp', timeStamp, defaultValue: Duration.zero, level: DiagnosticLevel.debug));
|
properties.add(DiagnosticsProperty<Duration>('timeStamp', timeStamp, defaultValue: Duration.zero, level: DiagnosticLevel.debug));
|
||||||
properties.add(IntProperty('pointer', pointer, level: DiagnosticLevel.debug));
|
properties.add(IntProperty('pointer', pointer, level: DiagnosticLevel.debug));
|
||||||
properties.add(EnumProperty<PointerDeviceKind>('kind', kind, level: DiagnosticLevel.debug));
|
properties.add(EnumProperty<PointerDeviceKind>('kind', kind, level: DiagnosticLevel.debug));
|
||||||
@ -423,6 +507,61 @@ abstract class PointerEvent extends Diagnosticable {
|
|||||||
String toStringFull() {
|
String toStringFull() {
|
||||||
return toString(minLevel: DiagnosticLevel.fine);
|
return toString(minLevel: DiagnosticLevel.fine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the transformation of `position` into the coordinate system
|
||||||
|
/// described by `transform`.
|
||||||
|
///
|
||||||
|
/// The z-value of `position` is assumed to be 0.0. If `transform` is null,
|
||||||
|
/// `position` is returned as-is.
|
||||||
|
static Offset transformPosition(Matrix4 transform, Offset position) {
|
||||||
|
if (transform == null) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
final Vector3 position3 = Vector3(position.dx, position.dy, 0.0);
|
||||||
|
final Vector3 transformed3 = transform.perspectiveTransform(position3);
|
||||||
|
return Offset(transformed3.x, transformed3.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms `untransformedDelta` into the coordinate system described by
|
||||||
|
/// `transform`.
|
||||||
|
///
|
||||||
|
/// It uses the provided `untransformedEndPosition` and
|
||||||
|
/// `transformedEndPosition` of the provided delta to increase accuracy.
|
||||||
|
///
|
||||||
|
/// If `transform` is null, `untransformedDelta` is returned.
|
||||||
|
static Offset transformDeltaViaPositions({
|
||||||
|
@required Offset untransformedEndPosition,
|
||||||
|
Offset transformedEndPosition,
|
||||||
|
@required Offset untransformedDelta,
|
||||||
|
@required Matrix4 transform,
|
||||||
|
}) {
|
||||||
|
if (transform == null) {
|
||||||
|
return untransformedDelta;
|
||||||
|
}
|
||||||
|
// We could transform the delta directly with the transformation matrix.
|
||||||
|
// While that is mathematically equivalent, in practice we are seeing a
|
||||||
|
// greater precision error with that approach. Instead, we are transforming
|
||||||
|
// start and end point of the delta separately and calculate the delta in
|
||||||
|
// the new space for greater accuracy.
|
||||||
|
transformedEndPosition ??= transformPosition(transform, untransformedEndPosition);
|
||||||
|
final Offset transformedStartPosition = transformPosition(transform, untransformedEndPosition - untransformedDelta);
|
||||||
|
return transformedEndPosition - transformedStartPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the "perspective" component from `transform`.
|
||||||
|
///
|
||||||
|
/// When applying the resulting transform matrix to a point with a
|
||||||
|
/// z-coordinate of zero (which is generally assumed for all points
|
||||||
|
/// represented by an [Offset]), the other coordinates will get transformed as
|
||||||
|
/// before, but the new z-coordinate is going to be zero again. This is
|
||||||
|
/// achieved by setting the third column and third row of the matrix to
|
||||||
|
/// "0, 0, 1, 0".
|
||||||
|
static Matrix4 removePerspectiveTransform(Matrix4 transform) {
|
||||||
|
final Vector4 vector = Vector4(0, 0, 1, 0);
|
||||||
|
return transform.clone()
|
||||||
|
..setColumn(2, vector)
|
||||||
|
..setRow(2, vector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The device has started tracking the pointer.
|
/// The device has started tracking the pointer.
|
||||||
@ -438,6 +577,7 @@ class PointerAddedEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressureMin = 1.0,
|
double pressureMin = 1.0,
|
||||||
double pressureMax = 1.0,
|
double pressureMax = 1.0,
|
||||||
@ -447,11 +587,14 @@ class PointerAddedEvent extends PointerEvent {
|
|||||||
double radiusMax = 0.0,
|
double radiusMax = 0.0,
|
||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerAddedEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
pressure: 0.0,
|
pressure: 0.0,
|
||||||
pressureMin: pressureMin,
|
pressureMin: pressureMin,
|
||||||
@ -462,7 +605,34 @@ class PointerAddedEvent extends PointerEvent {
|
|||||||
radiusMax: radiusMax,
|
radiusMax: radiusMax,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerAddedEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return PointerAddedEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: PointerEvent.transformPosition(transform, position),
|
||||||
|
obscured: obscured,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distance: distance,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The device is no longer tracking the pointer.
|
/// The device is no longer tracking the pointer.
|
||||||
@ -478,17 +648,21 @@ class PointerRemovedEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressureMin = 1.0,
|
double pressureMin = 1.0,
|
||||||
double pressureMax = 1.0,
|
double pressureMax = 1.0,
|
||||||
double distanceMax = 0.0,
|
double distanceMax = 0.0,
|
||||||
double radiusMin = 0.0,
|
double radiusMin = 0.0,
|
||||||
double radiusMax = 0.0,
|
double radiusMax = 0.0,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerRemovedEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
pressure: 0.0,
|
pressure: 0.0,
|
||||||
pressureMin: pressureMin,
|
pressureMin: pressureMin,
|
||||||
@ -496,7 +670,31 @@ class PointerRemovedEvent extends PointerEvent {
|
|||||||
distanceMax: distanceMax,
|
distanceMax: distanceMax,
|
||||||
radiusMin: radiusMin,
|
radiusMin: radiusMin,
|
||||||
radiusMax: radiusMax,
|
radiusMax: radiusMax,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerRemovedEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return PointerRemovedEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: PointerEvent.transformPosition(transform, position),
|
||||||
|
obscured: obscured,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer has moved with respect to the device while the pointer is not
|
/// The pointer has moved with respect to the device while the pointer is not
|
||||||
@ -518,7 +716,9 @@ class PointerHoverEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
Offset delta = Offset.zero,
|
Offset delta = Offset.zero,
|
||||||
|
Offset localDelta,
|
||||||
int buttons = 0,
|
int buttons = 0,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressureMin = 1.0,
|
double pressureMin = 1.0,
|
||||||
@ -533,12 +733,16 @@ class PointerHoverEvent extends PointerEvent {
|
|||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
bool synthesized = false,
|
bool synthesized = false,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerHoverEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
delta: delta,
|
delta: delta,
|
||||||
|
localDelta: localDelta,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: false,
|
down: false,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -555,7 +759,47 @@ class PointerHoverEvent extends PointerEvent {
|
|||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
synthesized: synthesized,
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerHoverEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
|
||||||
|
return PointerHoverEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: transformedPosition,
|
||||||
|
delta: delta,
|
||||||
|
localDelta: PointerEvent.transformDeltaViaPositions(
|
||||||
|
transform: transform,
|
||||||
|
untransformedDelta: delta,
|
||||||
|
untransformedEndPosition: position,
|
||||||
|
transformedEndPosition: transformedPosition,
|
||||||
|
),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distance: distance,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer has moved with respect to the device while the pointer is not
|
/// The pointer has moved with respect to the device while the pointer is not
|
||||||
@ -577,7 +821,9 @@ class PointerEnterEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
Offset delta = Offset.zero,
|
Offset delta = Offset.zero,
|
||||||
|
Offset localDelta,
|
||||||
int buttons = 0,
|
int buttons = 0,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressureMin = 1.0,
|
double pressureMin = 1.0,
|
||||||
@ -592,12 +838,16 @@ class PointerEnterEvent extends PointerEvent {
|
|||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
bool synthesized = false,
|
bool synthesized = false,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerEnterEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
delta: delta,
|
delta: delta,
|
||||||
|
localDelta: localDelta,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: false,
|
down: false,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -614,6 +864,8 @@ class PointerEnterEvent extends PointerEvent {
|
|||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
synthesized: synthesized,
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates an enter event from a [PointerHoverEvent].
|
/// Creates an enter event from a [PointerHoverEvent].
|
||||||
@ -630,7 +882,9 @@ class PointerEnterEvent extends PointerEvent {
|
|||||||
kind: event?.kind,
|
kind: event?.kind,
|
||||||
device: event?.device,
|
device: event?.device,
|
||||||
position: event?.position,
|
position: event?.position,
|
||||||
|
localPosition: event?.localPosition,
|
||||||
delta: event?.delta,
|
delta: event?.delta,
|
||||||
|
localDelta: event?.localDelta,
|
||||||
buttons: event?.buttons,
|
buttons: event?.buttons,
|
||||||
obscured: event?.obscured,
|
obscured: event?.obscured,
|
||||||
pressureMin: event?.pressureMin,
|
pressureMin: event?.pressureMin,
|
||||||
@ -645,7 +899,47 @@ class PointerEnterEvent extends PointerEvent {
|
|||||||
orientation: event?.orientation,
|
orientation: event?.orientation,
|
||||||
tilt: event?.tilt,
|
tilt: event?.tilt,
|
||||||
synthesized: event?.synthesized,
|
synthesized: event?.synthesized,
|
||||||
|
transform: event?.transform,
|
||||||
|
original: event?.original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerEnterEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
|
||||||
|
return PointerEnterEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: transformedPosition,
|
||||||
|
delta: delta,
|
||||||
|
localDelta: PointerEvent.transformDeltaViaPositions(
|
||||||
|
transform: transform,
|
||||||
|
untransformedDelta: delta,
|
||||||
|
untransformedEndPosition: position,
|
||||||
|
transformedEndPosition: transformedPosition,
|
||||||
|
),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distance: distance,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer has moved with respect to the device while the pointer is not
|
/// The pointer has moved with respect to the device while the pointer is not
|
||||||
@ -667,7 +961,9 @@ class PointerExitEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
Offset delta = Offset.zero,
|
Offset delta = Offset.zero,
|
||||||
|
Offset localDelta,
|
||||||
int buttons = 0,
|
int buttons = 0,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressureMin = 1.0,
|
double pressureMin = 1.0,
|
||||||
@ -682,12 +978,16 @@ class PointerExitEvent extends PointerEvent {
|
|||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
bool synthesized = false,
|
bool synthesized = false,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerExitEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
delta: delta,
|
delta: delta,
|
||||||
|
localDelta: localDelta,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: false,
|
down: false,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -704,6 +1004,8 @@ class PointerExitEvent extends PointerEvent {
|
|||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
synthesized: synthesized,
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates an exit event from a [PointerHoverEvent].
|
/// Creates an exit event from a [PointerHoverEvent].
|
||||||
@ -720,7 +1022,9 @@ class PointerExitEvent extends PointerEvent {
|
|||||||
kind: event?.kind,
|
kind: event?.kind,
|
||||||
device: event?.device,
|
device: event?.device,
|
||||||
position: event?.position,
|
position: event?.position,
|
||||||
|
localPosition: event?.localPosition,
|
||||||
delta: event?.delta,
|
delta: event?.delta,
|
||||||
|
localDelta: event?.localDelta,
|
||||||
buttons: event?.buttons,
|
buttons: event?.buttons,
|
||||||
obscured: event?.obscured,
|
obscured: event?.obscured,
|
||||||
pressureMin: event?.pressureMin,
|
pressureMin: event?.pressureMin,
|
||||||
@ -735,7 +1039,47 @@ class PointerExitEvent extends PointerEvent {
|
|||||||
orientation: event?.orientation,
|
orientation: event?.orientation,
|
||||||
tilt: event?.tilt,
|
tilt: event?.tilt,
|
||||||
synthesized: event?.synthesized,
|
synthesized: event?.synthesized,
|
||||||
|
transform: event?.transform,
|
||||||
|
original: event?.original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerExitEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
|
||||||
|
return PointerExitEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: transformedPosition,
|
||||||
|
delta: delta,
|
||||||
|
localDelta: PointerEvent.transformDeltaViaPositions(
|
||||||
|
transform: transform,
|
||||||
|
untransformedDelta: delta,
|
||||||
|
untransformedEndPosition: position,
|
||||||
|
transformedEndPosition: transformedPosition,
|
||||||
|
),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distance: distance,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer has made contact with the device.
|
/// The pointer has made contact with the device.
|
||||||
@ -749,6 +1093,7 @@ class PointerDownEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
int buttons = kPrimaryButton,
|
int buttons = kPrimaryButton,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressure = 1.0,
|
double pressure = 1.0,
|
||||||
@ -762,12 +1107,15 @@ class PointerDownEvent extends PointerEvent {
|
|||||||
double radiusMax = 0.0,
|
double radiusMax = 0.0,
|
||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerDownEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: true,
|
down: true,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -783,7 +1131,39 @@ class PointerDownEvent extends PointerEvent {
|
|||||||
radiusMax: radiusMax,
|
radiusMax: radiusMax,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerDownEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return PointerDownEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
pointer: pointer,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: PointerEvent.transformPosition(transform, position),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressure: pressure,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer has moved with respect to the device while the pointer is in
|
/// The pointer has moved with respect to the device while the pointer is in
|
||||||
@ -803,7 +1183,9 @@ class PointerMoveEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
Offset delta = Offset.zero,
|
Offset delta = Offset.zero,
|
||||||
|
Offset localDelta,
|
||||||
int buttons = kPrimaryButton,
|
int buttons = kPrimaryButton,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressure = 1.0,
|
double pressure = 1.0,
|
||||||
@ -819,13 +1201,17 @@ class PointerMoveEvent extends PointerEvent {
|
|||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
int platformData = 0,
|
int platformData = 0,
|
||||||
bool synthesized = false,
|
bool synthesized = false,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerMoveEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
delta: delta,
|
delta: delta,
|
||||||
|
localDelta: localDelta,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: true,
|
down: true,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -843,7 +1229,50 @@ class PointerMoveEvent extends PointerEvent {
|
|||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
platformData: platformData,
|
platformData: platformData,
|
||||||
synthesized: synthesized,
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerMoveEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
|
||||||
|
|
||||||
|
return PointerMoveEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
pointer: pointer,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: transformedPosition,
|
||||||
|
delta: delta,
|
||||||
|
localDelta: PointerEvent.transformDeltaViaPositions(
|
||||||
|
transform: transform,
|
||||||
|
untransformedDelta: delta,
|
||||||
|
untransformedEndPosition: position,
|
||||||
|
transformedEndPosition: transformedPosition,
|
||||||
|
),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressure: pressure,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
platformData: platformData,
|
||||||
|
synthesized: synthesized,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer has stopped making contact with the device.
|
/// The pointer has stopped making contact with the device.
|
||||||
@ -857,6 +1286,7 @@ class PointerUpEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
int buttons = 0,
|
int buttons = 0,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
// Allow pressure customization here because PointerUpEvent can contain
|
// Allow pressure customization here because PointerUpEvent can contain
|
||||||
@ -873,12 +1303,15 @@ class PointerUpEvent extends PointerEvent {
|
|||||||
double radiusMax = 0.0,
|
double radiusMax = 0.0,
|
||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerUpEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: false,
|
down: false,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -894,7 +1327,40 @@ class PointerUpEvent extends PointerEvent {
|
|||||||
radiusMax: radiusMax,
|
radiusMax: radiusMax,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerUpEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return PointerUpEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
pointer: pointer,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: PointerEvent.transformPosition(transform, position),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressure: pressure,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distance: distance,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An event that corresponds to a discrete pointer signal.
|
/// An event that corresponds to a discrete pointer signal.
|
||||||
@ -911,12 +1377,18 @@ abstract class PointerSignalEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.mouse,
|
PointerDeviceKind kind = PointerDeviceKind.mouse,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerSignalEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -933,7 +1405,10 @@ class PointerScrollEvent extends PointerSignalEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.mouse,
|
PointerDeviceKind kind = PointerDeviceKind.mouse,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
this.scrollDelta = Offset.zero,
|
this.scrollDelta = Offset.zero,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerScrollEvent original,
|
||||||
}) : assert(timeStamp != null),
|
}) : assert(timeStamp != null),
|
||||||
assert(kind != null),
|
assert(kind != null),
|
||||||
assert(device != null),
|
assert(device != null),
|
||||||
@ -944,11 +1419,31 @@ class PointerScrollEvent extends PointerSignalEvent {
|
|||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The amount to scroll, in logical pixels.
|
/// The amount to scroll, in logical pixels.
|
||||||
final Offset scrollDelta;
|
final Offset scrollDelta;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerScrollEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return PointerScrollEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: PointerEvent.transformPosition(transform, position),
|
||||||
|
scrollDelta: scrollDelta,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
@ -967,6 +1462,7 @@ class PointerCancelEvent extends PointerEvent {
|
|||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device = 0,
|
int device = 0,
|
||||||
Offset position = Offset.zero,
|
Offset position = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
int buttons = 0,
|
int buttons = 0,
|
||||||
bool obscured = false,
|
bool obscured = false,
|
||||||
double pressureMin = 1.0,
|
double pressureMin = 1.0,
|
||||||
@ -980,12 +1476,15 @@ class PointerCancelEvent extends PointerEvent {
|
|||||||
double radiusMax = 0.0,
|
double radiusMax = 0.0,
|
||||||
double orientation = 0.0,
|
double orientation = 0.0,
|
||||||
double tilt = 0.0,
|
double tilt = 0.0,
|
||||||
|
Matrix4 transform,
|
||||||
|
PointerCancelEvent original,
|
||||||
}) : super(
|
}) : super(
|
||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: device,
|
device: device,
|
||||||
position: position,
|
position: position,
|
||||||
|
localPosition: localPosition,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
down: false,
|
down: false,
|
||||||
obscured: obscured,
|
obscured: obscured,
|
||||||
@ -1001,5 +1500,37 @@ class PointerCancelEvent extends PointerEvent {
|
|||||||
radiusMax: radiusMax,
|
radiusMax: radiusMax,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
tilt: tilt,
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerCancelEvent transformed(Matrix4 transform) {
|
||||||
|
if (transform == null || transform == this.transform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return PointerCancelEvent(
|
||||||
|
timeStamp: timeStamp,
|
||||||
|
pointer: pointer,
|
||||||
|
kind: kind,
|
||||||
|
device: device,
|
||||||
|
position: position,
|
||||||
|
localPosition: PointerEvent.transformPosition(transform, position),
|
||||||
|
buttons: buttons,
|
||||||
|
obscured: obscured,
|
||||||
|
pressureMin: pressureMin,
|
||||||
|
pressureMax: pressureMax,
|
||||||
|
distance: distance,
|
||||||
|
distanceMax: distanceMax,
|
||||||
|
size: size,
|
||||||
|
radiusMajor: radiusMajor,
|
||||||
|
radiusMinor: radiusMinor,
|
||||||
|
radiusMin: radiusMin,
|
||||||
|
radiusMax: radiusMax,
|
||||||
|
orientation: orientation,
|
||||||
|
tilt: tilt,
|
||||||
|
transform: transform,
|
||||||
|
original: original ?? this,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -51,13 +51,18 @@ class ForcePressDetails {
|
|||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
ForcePressDetails({
|
ForcePressDetails({
|
||||||
@required this.globalPosition,
|
@required this.globalPosition,
|
||||||
|
Offset localPosition,
|
||||||
@required this.pressure,
|
@required this.pressure,
|
||||||
}) : assert(globalPosition != null),
|
}) : assert(globalPosition != null),
|
||||||
assert(pressure != null);
|
assert(pressure != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// The global position at which the function was called.
|
/// The global position at which the function was called.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position at which the function was called.
|
||||||
|
final Offset localPosition;
|
||||||
|
|
||||||
/// The pressure of the pointer on the screen.
|
/// The pressure of the pointer on the screen.
|
||||||
final double pressure;
|
final double pressure;
|
||||||
}
|
}
|
||||||
@ -203,7 +208,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
/// ```
|
/// ```
|
||||||
final GestureForceInterpolation interpolation;
|
final GestureForceInterpolation interpolation;
|
||||||
|
|
||||||
Offset _lastPosition;
|
OffsetPair _lastPosition;
|
||||||
double _lastPressure;
|
double _lastPressure;
|
||||||
_ForceState _state = _ForceState.ready;
|
_ForceState _state = _ForceState.ready;
|
||||||
|
|
||||||
@ -215,10 +220,10 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
if (!(event is PointerUpEvent) && event.pressureMax <= 1.0) {
|
if (!(event is PointerUpEvent) && event.pressureMax <= 1.0) {
|
||||||
resolve(GestureDisposition.rejected);
|
resolve(GestureDisposition.rejected);
|
||||||
} else {
|
} else {
|
||||||
startTrackingPointer(event.pointer);
|
startTrackingPointer(event.pointer, event.transform);
|
||||||
if (_state == _ForceState.ready) {
|
if (_state == _ForceState.ready) {
|
||||||
_state = _ForceState.possible;
|
_state = _ForceState.possible;
|
||||||
_lastPosition = event.position;
|
_lastPosition = OffsetPair.fromEventPosition(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,7 +247,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
pressure.isNaN // and interpolation may return NaN for values it doesn't want to support...
|
pressure.isNaN // and interpolation may return NaN for values it doesn't want to support...
|
||||||
);
|
);
|
||||||
|
|
||||||
_lastPosition = event.position;
|
_lastPosition = OffsetPair.fromEventPosition(event);
|
||||||
_lastPressure = pressure;
|
_lastPressure = pressure;
|
||||||
|
|
||||||
if (_state == _ForceState.possible) {
|
if (_state == _ForceState.possible) {
|
||||||
@ -260,7 +265,8 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
if (onStart != null) {
|
if (onStart != null) {
|
||||||
invokeCallback<void>('onStart', () => onStart(ForcePressDetails(
|
invokeCallback<void>('onStart', () => onStart(ForcePressDetails(
|
||||||
pressure: pressure,
|
pressure: pressure,
|
||||||
globalPosition: _lastPosition,
|
globalPosition: _lastPosition.global,
|
||||||
|
localPosition: _lastPosition.local,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,6 +277,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
invokeCallback<void>('onPeak', () => onPeak(ForcePressDetails(
|
invokeCallback<void>('onPeak', () => onPeak(ForcePressDetails(
|
||||||
pressure: pressure,
|
pressure: pressure,
|
||||||
globalPosition: event.position,
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,6 +287,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
invokeCallback<void>('onUpdate', () => onUpdate(ForcePressDetails(
|
invokeCallback<void>('onUpdate', () => onUpdate(ForcePressDetails(
|
||||||
pressure: pressure,
|
pressure: pressure,
|
||||||
globalPosition: event.position,
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,7 +303,8 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
if (onStart != null && _state == _ForceState.started) {
|
if (onStart != null && _state == _ForceState.started) {
|
||||||
invokeCallback<void>('onStart', () => onStart(ForcePressDetails(
|
invokeCallback<void>('onStart', () => onStart(ForcePressDetails(
|
||||||
pressure: _lastPressure,
|
pressure: _lastPressure,
|
||||||
globalPosition: _lastPosition,
|
globalPosition: _lastPosition.global,
|
||||||
|
localPosition: _lastPosition.local,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,7 +320,8 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
if (onEnd != null) {
|
if (onEnd != null) {
|
||||||
invokeCallback<void>('onEnd', () => onEnd(ForcePressDetails(
|
invokeCallback<void>('onEnd', () => onEnd(ForcePressDetails(
|
||||||
pressure: 0.0,
|
pressure: 0.0,
|
||||||
globalPosition: _lastPosition,
|
globalPosition: _lastPosition.global,
|
||||||
|
localPosition: _lastPosition.local,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import 'events.dart';
|
import 'events.dart';
|
||||||
|
|
||||||
/// An object that can hit-test pointers.
|
/// An object that can hit-test pointers.
|
||||||
@ -43,19 +48,32 @@ abstract class HitTestTarget {
|
|||||||
/// to the event propagation phase.
|
/// to the event propagation phase.
|
||||||
class HitTestEntry {
|
class HitTestEntry {
|
||||||
/// Creates a hit test entry.
|
/// Creates a hit test entry.
|
||||||
const HitTestEntry(this.target);
|
HitTestEntry(this.target);
|
||||||
|
|
||||||
/// The [HitTestTarget] encountered during the hit test.
|
/// The [HitTestTarget] encountered during the hit test.
|
||||||
final HitTestTarget target;
|
final HitTestTarget target;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$target';
|
String toString() => '$target';
|
||||||
|
|
||||||
|
/// Returns a matrix describing how [PointerEvent]s delivered to this
|
||||||
|
/// [HitTestEntry] should be transformed from the global coordinate space of
|
||||||
|
/// the screen to the local coordinate space of [target].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [HitTestResult.addWithPaintTransform], which is used during hit testing
|
||||||
|
/// to build up the transform returned by this method.
|
||||||
|
Matrix4 get transform => _transform;
|
||||||
|
Matrix4 _transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of performing a hit test.
|
/// The result of performing a hit test.
|
||||||
class HitTestResult {
|
class HitTestResult {
|
||||||
/// Creates an empty hit test result.
|
/// Creates an empty hit test result.
|
||||||
HitTestResult() : _path = <HitTestEntry>[];
|
HitTestResult()
|
||||||
|
: _path = <HitTestEntry>[],
|
||||||
|
_transforms = Queue<Matrix4>();
|
||||||
|
|
||||||
/// Wraps `result` (usually a subtype of [HitTestResult]) to create a
|
/// Wraps `result` (usually a subtype of [HitTestResult]) to create a
|
||||||
/// generic [HitTestResult].
|
/// generic [HitTestResult].
|
||||||
@ -63,7 +81,9 @@ class HitTestResult {
|
|||||||
/// The [HitTestEntry]s added to the returned [HitTestResult] are also
|
/// The [HitTestEntry]s added to the returned [HitTestResult] are also
|
||||||
/// added to the wrapped `result` (both share the same underlying data
|
/// added to the wrapped `result` (both share the same underlying data
|
||||||
/// structure to store [HitTestEntry]s).
|
/// structure to store [HitTestEntry]s).
|
||||||
HitTestResult.wrap(HitTestResult result) : _path = result._path;
|
HitTestResult.wrap(HitTestResult result)
|
||||||
|
: _path = result._path,
|
||||||
|
_transforms = result._transforms;
|
||||||
|
|
||||||
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
|
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
|
||||||
///
|
///
|
||||||
@ -73,15 +93,87 @@ class HitTestResult {
|
|||||||
Iterable<HitTestEntry> get path => _path;
|
Iterable<HitTestEntry> get path => _path;
|
||||||
final List<HitTestEntry> _path;
|
final List<HitTestEntry> _path;
|
||||||
|
|
||||||
|
final Queue<Matrix4> _transforms;
|
||||||
|
|
||||||
/// Add a [HitTestEntry] to the path.
|
/// Add a [HitTestEntry] to the path.
|
||||||
///
|
///
|
||||||
/// The new entry is added at the end of the path, which means entries should
|
/// The new entry is added at the end of the path, which means entries should
|
||||||
/// be added in order from most specific to least specific, typically during an
|
/// be added in order from most specific to least specific, typically during an
|
||||||
/// upward walk of the tree being hit tested.
|
/// upward walk of the tree being hit tested.
|
||||||
void add(HitTestEntry entry) {
|
void add(HitTestEntry entry) {
|
||||||
|
assert(entry._transform == null);
|
||||||
|
entry._transform = _transforms.isEmpty ? null : _transforms.last;
|
||||||
_path.add(entry);
|
_path.add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pushes a new transform matrix that is to be applied to all future
|
||||||
|
/// [HitTestEntry]s added via [add] until it is removed via [popTransform].
|
||||||
|
///
|
||||||
|
/// This method is only to be used by subclasses, which must provide
|
||||||
|
/// coordinate space specific public wrappers around this function for their
|
||||||
|
/// users (see [BoxHitTestResult.addWithPaintTransform] for such an example).
|
||||||
|
///
|
||||||
|
/// The provided `transform` matrix should describe how to transform
|
||||||
|
/// [PointerEvent]s from the coordinate space of the method caller to the
|
||||||
|
/// coordinate space of its children. In most cases `transform` is derived
|
||||||
|
/// from running the inverted result of [RenderObject.applyPaintTransform]
|
||||||
|
/// through [PointerEvent.removePerspectiveTransform] to remove
|
||||||
|
/// the perspective component.
|
||||||
|
///
|
||||||
|
/// [HitTestable]s need to call this method indirectly through a convenience
|
||||||
|
/// method defined on a subclass before hit testing a child that does not
|
||||||
|
/// have the same origin as the parent. After hit testing the child,
|
||||||
|
/// [popTransform] has to be called to remove the child-specific `transform`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [BoxHitTestResult.addWithPaintTransform], which is a public wrapper
|
||||||
|
/// around this function for hit testing on [RenderBox]s.
|
||||||
|
/// * [SliverHitTestResult.addWithAxisOffset], which is a public wrapper
|
||||||
|
/// around this function for hit testing on [RenderSlivers]s.
|
||||||
|
@protected
|
||||||
|
void pushTransform(Matrix4 transform) {
|
||||||
|
assert(transform != null);
|
||||||
|
assert(
|
||||||
|
_debugVectorMoreOrLessEquals(transform.getRow(2), Vector4(0, 0, 1, 0)) &&
|
||||||
|
_debugVectorMoreOrLessEquals(transform.getColumn(2), Vector4(0, 0, 1, 0)),
|
||||||
|
'The third row and third column of a transform matrix for pointer '
|
||||||
|
'events must be Vector4(0, 0, 1, 0) to ensure that a transformed '
|
||||||
|
'point is directly under the pointer device. Did you forget to run the paint '
|
||||||
|
'matrix through PointerEvent.removePerspectiveTransform?'
|
||||||
|
'The provided matrix is:\n$transform'
|
||||||
|
);
|
||||||
|
_transforms.add(_transforms.isEmpty ? transform : transform * _transforms.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the last transform added via [pushTransform].
|
||||||
|
///
|
||||||
|
/// This method is only to be used by subclasses, which must provide
|
||||||
|
/// coordinate space specific public wrappers around this function for their
|
||||||
|
/// users (see [BoxHitTestResult.addWithPaintTransform] for such an example).
|
||||||
|
///
|
||||||
|
/// This method must be called after hit testing is done on a child that
|
||||||
|
/// required a call to [pushTransform].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [pushTransform], which describes the use case of this function pair in
|
||||||
|
/// more details.
|
||||||
|
@protected
|
||||||
|
void popTransform() {
|
||||||
|
assert(_transforms.isNotEmpty);
|
||||||
|
_transforms.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _debugVectorMoreOrLessEquals(Vector4 a, Vector4 b, { double epsilon = precisionErrorTolerance }) {
|
||||||
|
bool result = true;
|
||||||
|
assert(() {
|
||||||
|
final Vector4 difference = a - b;
|
||||||
|
result = difference.storage.every((double component) => component.abs() < epsilon);
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'HitTestResult(${_path.isEmpty ? "<empty path>" : _path.join(", ")})';
|
String toString() => 'HitTestResult(${_path.isEmpty ? "<empty path>" : _path.join(", ")})';
|
||||||
}
|
}
|
||||||
|
@ -51,11 +51,17 @@ class LongPressStartDetails {
|
|||||||
/// Creates the details for a [GestureLongPressStartCallback].
|
/// Creates the details for a [GestureLongPressStartCallback].
|
||||||
///
|
///
|
||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
const LongPressStartDetails({ this.globalPosition = Offset.zero })
|
const LongPressStartDetails({
|
||||||
: assert(globalPosition != null);
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
|
}) : assert(globalPosition != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// The global position at which the pointer contacted the screen.
|
/// The global position at which the pointer contacted the screen.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position at which the pointer contacted the screen.
|
||||||
|
final Offset localPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details for callbacks that use [GestureLongPressMoveUpdateCallback].
|
/// Details for callbacks that use [GestureLongPressMoveUpdateCallback].
|
||||||
@ -71,17 +77,29 @@ class LongPressMoveUpdateDetails {
|
|||||||
/// The [globalPosition] and [offsetFromOrigin] arguments must not be null.
|
/// The [globalPosition] and [offsetFromOrigin] arguments must not be null.
|
||||||
const LongPressMoveUpdateDetails({
|
const LongPressMoveUpdateDetails({
|
||||||
this.globalPosition = Offset.zero,
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
this.offsetFromOrigin = Offset.zero,
|
this.offsetFromOrigin = Offset.zero,
|
||||||
|
Offset localOffsetFromOrigin,
|
||||||
}) : assert(globalPosition != null),
|
}) : assert(globalPosition != null),
|
||||||
assert(offsetFromOrigin != null);
|
assert(offsetFromOrigin != null),
|
||||||
|
localPosition = localPosition ?? globalPosition,
|
||||||
|
localOffsetFromOrigin = localOffsetFromOrigin ?? offsetFromOrigin;
|
||||||
|
|
||||||
/// The global position of the pointer when it triggered this update.
|
/// The global position of the pointer when it triggered this update.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position of the pointer when it triggered this update.
|
||||||
|
final Offset localPosition;
|
||||||
|
|
||||||
/// A delta offset from the point where the long press drag initially contacted
|
/// A delta offset from the point where the long press drag initially contacted
|
||||||
/// the screen to the point where the pointer is currently located (the
|
/// the screen to the point where the pointer is currently located (the
|
||||||
/// present [globalPosition]) when this callback is triggered.
|
/// present [globalPosition]) when this callback is triggered.
|
||||||
final Offset offsetFromOrigin;
|
final Offset offsetFromOrigin;
|
||||||
|
|
||||||
|
/// A local delta offset from the point where the long press drag initially contacted
|
||||||
|
/// the screen to the point where the pointer is currently located (the
|
||||||
|
/// present [localPosition]) when this callback is triggered.
|
||||||
|
final Offset localOffsetFromOrigin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details for callbacks that use [GestureLongPressEndCallback].
|
/// Details for callbacks that use [GestureLongPressEndCallback].
|
||||||
@ -95,11 +113,17 @@ class LongPressEndDetails {
|
|||||||
/// Creates the details for a [GestureLongPressEndCallback].
|
/// Creates the details for a [GestureLongPressEndCallback].
|
||||||
///
|
///
|
||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
const LongPressEndDetails({ this.globalPosition = Offset.zero })
|
const LongPressEndDetails({
|
||||||
: assert(globalPosition != null);
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
|
}) : assert(globalPosition != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// The global position at which the pointer lifted from the screen.
|
/// The global position at which the pointer lifted from the screen.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position at which the pointer contacted the screen.
|
||||||
|
final Offset localPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recognizes when the user has pressed down at the same location for a long
|
/// Recognizes when the user has pressed down at the same location for a long
|
||||||
@ -137,7 +161,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
bool _longPressAccepted = false;
|
bool _longPressAccepted = false;
|
||||||
Offset _longPressOrigin;
|
OffsetPair _longPressOrigin;
|
||||||
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
|
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
|
||||||
// different set of buttons, the gesture is canceled.
|
// different set of buttons, the gesture is canceled.
|
||||||
int _initialButtons;
|
int _initialButtons;
|
||||||
@ -230,7 +254,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
_reset();
|
_reset();
|
||||||
} else if (event is PointerDownEvent) {
|
} else if (event is PointerDownEvent) {
|
||||||
// The first touch.
|
// The first touch.
|
||||||
_longPressOrigin = event.position;
|
_longPressOrigin = OffsetPair.fromEventPosition(event);
|
||||||
_initialButtons = event.buttons;
|
_initialButtons = event.buttons;
|
||||||
} else if (event is PointerMoveEvent) {
|
} else if (event is PointerMoveEvent) {
|
||||||
if (event.buttons != _initialButtons) {
|
if (event.buttons != _initialButtons) {
|
||||||
@ -245,7 +269,8 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
void _checkLongPressStart() {
|
void _checkLongPressStart() {
|
||||||
assert(_initialButtons == kPrimaryButton);
|
assert(_initialButtons == kPrimaryButton);
|
||||||
final LongPressStartDetails details = LongPressStartDetails(
|
final LongPressStartDetails details = LongPressStartDetails(
|
||||||
globalPosition: _longPressOrigin,
|
globalPosition: _longPressOrigin.global,
|
||||||
|
localPosition: _longPressOrigin.local,
|
||||||
);
|
);
|
||||||
if (onLongPressStart != null)
|
if (onLongPressStart != null)
|
||||||
invokeCallback<void>('onLongPressStart',
|
invokeCallback<void>('onLongPressStart',
|
||||||
@ -258,7 +283,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
assert(_initialButtons == kPrimaryButton);
|
assert(_initialButtons == kPrimaryButton);
|
||||||
final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails(
|
final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails(
|
||||||
globalPosition: event.position,
|
globalPosition: event.position,
|
||||||
offsetFromOrigin: event.position - _longPressOrigin,
|
localPosition: event.localPosition,
|
||||||
|
offsetFromOrigin: event.position - _longPressOrigin.global,
|
||||||
|
localOffsetFromOrigin: event.localPosition - _longPressOrigin.local,
|
||||||
);
|
);
|
||||||
if (onLongPressMoveUpdate != null)
|
if (onLongPressMoveUpdate != null)
|
||||||
invokeCallback<void>('onLongPressMoveUpdate',
|
invokeCallback<void>('onLongPressMoveUpdate',
|
||||||
@ -269,6 +296,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
assert(_initialButtons == kPrimaryButton);
|
assert(_initialButtons == kPrimaryButton);
|
||||||
final LongPressEndDetails details = LongPressEndDetails(
|
final LongPressEndDetails details = LongPressEndDetails(
|
||||||
globalPosition: event.position,
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
);
|
);
|
||||||
if (onLongPressEnd != null)
|
if (onLongPressEnd != null)
|
||||||
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details));
|
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details));
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import 'arena.dart';
|
import 'arena.dart';
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
@ -169,17 +170,24 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
double maxFlingVelocity;
|
double maxFlingVelocity;
|
||||||
|
|
||||||
_DragState _state = _DragState.ready;
|
_DragState _state = _DragState.ready;
|
||||||
Offset _initialPosition;
|
OffsetPair _initialPosition;
|
||||||
Offset _pendingDragOffset;
|
OffsetPair _pendingDragOffset;
|
||||||
Duration _lastPendingEventTimestamp;
|
Duration _lastPendingEventTimestamp;
|
||||||
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
|
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
|
||||||
// different set of buttons, the gesture is canceled.
|
// different set of buttons, the gesture is canceled.
|
||||||
int _initialButtons;
|
int _initialButtons;
|
||||||
|
Matrix4 _lastTransform;
|
||||||
|
|
||||||
|
/// Distance moved in the global coordinate space of the screen in drag direction.
|
||||||
|
///
|
||||||
|
/// If drag is only allowed along a defined axis, this value may be negative to
|
||||||
|
/// differentiate the direction of the drag.
|
||||||
|
double _globalDistanceMoved;
|
||||||
|
|
||||||
bool _isFlingGesture(VelocityEstimate estimate);
|
bool _isFlingGesture(VelocityEstimate estimate);
|
||||||
Offset _getDeltaForDetails(Offset delta);
|
Offset _getDeltaForDetails(Offset delta);
|
||||||
double _getPrimaryValueFromOffset(Offset value);
|
double _getPrimaryValueFromOffset(Offset value);
|
||||||
bool get _hasSufficientPendingDragDeltaToAccept;
|
bool get _hasSufficientGlobalDistanceToAccept;
|
||||||
|
|
||||||
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
|
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
|
||||||
|
|
||||||
@ -209,14 +217,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerEvent event) {
|
void addAllowedPointer(PointerEvent event) {
|
||||||
startTrackingPointer(event.pointer);
|
startTrackingPointer(event.pointer, event.transform);
|
||||||
_velocityTrackers[event.pointer] = VelocityTracker();
|
_velocityTrackers[event.pointer] = VelocityTracker();
|
||||||
if (_state == _DragState.ready) {
|
if (_state == _DragState.ready) {
|
||||||
_state = _DragState.possible;
|
_state = _DragState.possible;
|
||||||
_initialPosition = event.position;
|
_initialPosition = OffsetPair(global: event.position, local: event.localPosition);
|
||||||
_initialButtons = event.buttons;
|
_initialButtons = event.buttons;
|
||||||
_pendingDragOffset = Offset.zero;
|
_pendingDragOffset = OffsetPair.zero;
|
||||||
|
_globalDistanceMoved = 0.0;
|
||||||
_lastPendingEventTimestamp = event.timeStamp;
|
_lastPendingEventTimestamp = event.timeStamp;
|
||||||
|
_lastTransform = event.transform;
|
||||||
_checkDown();
|
_checkDown();
|
||||||
} else if (_state == _DragState.accepted) {
|
} else if (_state == _DragState.accepted) {
|
||||||
resolve(GestureDisposition.accepted);
|
resolve(GestureDisposition.accepted);
|
||||||
@ -230,7 +240,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
&& (event is PointerDownEvent || event is PointerMoveEvent)) {
|
&& (event is PointerDownEvent || event is PointerMoveEvent)) {
|
||||||
final VelocityTracker tracker = _velocityTrackers[event.pointer];
|
final VelocityTracker tracker = _velocityTrackers[event.pointer];
|
||||||
assert(tracker != null);
|
assert(tracker != null);
|
||||||
tracker.addPosition(event.timeStamp, event.position);
|
tracker.addPosition(event.timeStamp, event.localPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event is PointerMoveEvent) {
|
if (event is PointerMoveEvent) {
|
||||||
@ -239,18 +249,26 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
stopTrackingPointer(event.pointer);
|
stopTrackingPointer(event.pointer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Offset delta = event.delta;
|
|
||||||
if (_state == _DragState.accepted) {
|
if (_state == _DragState.accepted) {
|
||||||
_checkUpdate(
|
_checkUpdate(
|
||||||
sourceTimeStamp: event.timeStamp,
|
sourceTimeStamp: event.timeStamp,
|
||||||
delta: _getDeltaForDetails(delta),
|
delta: _getDeltaForDetails(event.localDelta),
|
||||||
primaryDelta: _getPrimaryValueFromOffset(delta),
|
primaryDelta: _getPrimaryValueFromOffset(event.localDelta),
|
||||||
globalPosition: event.position,
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_pendingDragOffset += delta;
|
_pendingDragOffset += OffsetPair(local: event.localDelta, global: event.delta);
|
||||||
_lastPendingEventTimestamp = event.timeStamp;
|
_lastPendingEventTimestamp = event.timeStamp;
|
||||||
if (_hasSufficientPendingDragDeltaToAccept)
|
_lastTransform = event.transform;
|
||||||
|
final Offset movedLocally = _getDeltaForDetails(event.localDelta);
|
||||||
|
final Matrix4 localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform);
|
||||||
|
_globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
|
||||||
|
transform: localToGlobalTransform,
|
||||||
|
untransformedDelta: movedLocally,
|
||||||
|
untransformedEndPosition: event.localPosition,
|
||||||
|
).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
|
||||||
|
if (_hasSufficientGlobalDistanceToAccept)
|
||||||
resolve(GestureDisposition.accepted);
|
resolve(GestureDisposition.accepted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,27 +279,39 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
void acceptGesture(int pointer) {
|
void acceptGesture(int pointer) {
|
||||||
if (_state != _DragState.accepted) {
|
if (_state != _DragState.accepted) {
|
||||||
_state = _DragState.accepted;
|
_state = _DragState.accepted;
|
||||||
final Offset delta = _pendingDragOffset;
|
final OffsetPair delta = _pendingDragOffset;
|
||||||
final Duration timestamp = _lastPendingEventTimestamp;
|
final Duration timestamp = _lastPendingEventTimestamp;
|
||||||
Offset updateDelta;
|
final Matrix4 transform = _lastTransform;
|
||||||
|
Offset localUpdateDelta;
|
||||||
switch (dragStartBehavior) {
|
switch (dragStartBehavior) {
|
||||||
case DragStartBehavior.start:
|
case DragStartBehavior.start:
|
||||||
_initialPosition = _initialPosition + delta;
|
_initialPosition = _initialPosition + delta;
|
||||||
updateDelta = Offset.zero;
|
localUpdateDelta = Offset.zero;
|
||||||
break;
|
break;
|
||||||
case DragStartBehavior.down:
|
case DragStartBehavior.down:
|
||||||
updateDelta = _getDeltaForDetails(delta);
|
localUpdateDelta = _getDeltaForDetails(delta.local);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_pendingDragOffset = Offset.zero;
|
_pendingDragOffset = OffsetPair.zero;
|
||||||
_lastPendingEventTimestamp = null;
|
_lastPendingEventTimestamp = null;
|
||||||
|
_lastTransform = null;
|
||||||
_checkStart(timestamp);
|
_checkStart(timestamp);
|
||||||
if (updateDelta != Offset.zero) {
|
if (localUpdateDelta != Offset.zero && onUpdate != null) {
|
||||||
|
final Matrix4 localToGlobal = transform != null ? Matrix4.tryInvert(transform) : null;
|
||||||
|
final Offset correctedLocalPosition = _initialPosition.local + localUpdateDelta;
|
||||||
|
final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions(
|
||||||
|
untransformedEndPosition: correctedLocalPosition,
|
||||||
|
untransformedDelta: localUpdateDelta,
|
||||||
|
transform: localToGlobal,
|
||||||
|
);
|
||||||
|
final OffsetPair updateDelta = OffsetPair(local: localUpdateDelta, global: globalUpdateDelta);
|
||||||
|
final OffsetPair correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour
|
||||||
_checkUpdate(
|
_checkUpdate(
|
||||||
sourceTimeStamp: timestamp,
|
sourceTimeStamp: timestamp,
|
||||||
delta: updateDelta,
|
delta: localUpdateDelta,
|
||||||
primaryDelta: _getPrimaryValueFromOffset(updateDelta),
|
primaryDelta: _getPrimaryValueFromOffset(localUpdateDelta),
|
||||||
globalPosition: _initialPosition + updateDelta, // Only adds delta for down behavior
|
globalPosition: correctedPosition.global,
|
||||||
|
localPosition: correctedPosition.local,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,7 +346,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
void _checkDown() {
|
void _checkDown() {
|
||||||
assert(_initialButtons == kPrimaryButton);
|
assert(_initialButtons == kPrimaryButton);
|
||||||
final DragDownDetails details = DragDownDetails(
|
final DragDownDetails details = DragDownDetails(
|
||||||
globalPosition: _initialPosition,
|
globalPosition: _initialPosition.global,
|
||||||
|
localPosition: _initialPosition.local,
|
||||||
);
|
);
|
||||||
if (onDown != null)
|
if (onDown != null)
|
||||||
invokeCallback<void>('onDown', () => onDown(details));
|
invokeCallback<void>('onDown', () => onDown(details));
|
||||||
@ -326,7 +357,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
assert(_initialButtons == kPrimaryButton);
|
assert(_initialButtons == kPrimaryButton);
|
||||||
final DragStartDetails details = DragStartDetails(
|
final DragStartDetails details = DragStartDetails(
|
||||||
sourceTimeStamp: timestamp,
|
sourceTimeStamp: timestamp,
|
||||||
globalPosition: _initialPosition,
|
globalPosition: _initialPosition.global,
|
||||||
|
localPosition: _initialPosition.local,
|
||||||
);
|
);
|
||||||
if (onStart != null)
|
if (onStart != null)
|
||||||
invokeCallback<void>('onStart', () => onStart(details));
|
invokeCallback<void>('onStart', () => onStart(details));
|
||||||
@ -337,6 +369,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
Offset delta,
|
Offset delta,
|
||||||
double primaryDelta,
|
double primaryDelta,
|
||||||
Offset globalPosition,
|
Offset globalPosition,
|
||||||
|
Offset localPosition,
|
||||||
}) {
|
}) {
|
||||||
assert(_initialButtons == kPrimaryButton);
|
assert(_initialButtons == kPrimaryButton);
|
||||||
final DragUpdateDetails details = DragUpdateDetails(
|
final DragUpdateDetails details = DragUpdateDetails(
|
||||||
@ -344,6 +377,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
delta: delta,
|
delta: delta,
|
||||||
primaryDelta: primaryDelta,
|
primaryDelta: primaryDelta,
|
||||||
globalPosition: globalPosition,
|
globalPosition: globalPosition,
|
||||||
|
localPosition: localPosition,
|
||||||
);
|
);
|
||||||
if (onUpdate != null)
|
if (onUpdate != null)
|
||||||
invokeCallback<void>('onUpdate', () => onUpdate(details));
|
invokeCallback<void>('onUpdate', () => onUpdate(details));
|
||||||
@ -430,7 +464,7 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop;
|
bool get _hasSufficientGlobalDistanceToAccept => _globalDistanceMoved.abs() > kTouchSlop;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy);
|
Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy);
|
||||||
@ -469,7 +503,7 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop;
|
bool get _hasSufficientGlobalDistanceToAccept => _globalDistanceMoved.abs() > kTouchSlop;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0);
|
Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0);
|
||||||
@ -503,8 +537,8 @@ class PanGestureRecognizer extends DragGestureRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get _hasSufficientPendingDragDeltaToAccept {
|
bool get _hasSufficientGlobalDistanceToAccept {
|
||||||
return _pendingDragOffset.distance > kPanSlop;
|
return _globalDistanceMoved.abs() > kPanSlop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui' show Offset;
|
import 'dart:ui' show Offset;
|
||||||
import 'package:flutter/foundation.dart' show required;
|
import 'package:flutter/foundation.dart' show required;
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import 'arena.dart';
|
import 'arena.dart';
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
@ -66,22 +67,22 @@ class _TapTracker {
|
|||||||
assert(event != null),
|
assert(event != null),
|
||||||
assert(event.buttons != null),
|
assert(event.buttons != null),
|
||||||
pointer = event.pointer,
|
pointer = event.pointer,
|
||||||
_initialPosition = event.position,
|
_initialGlobalPosition = event.position,
|
||||||
initialButtons = event.buttons,
|
initialButtons = event.buttons,
|
||||||
_doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime);
|
_doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime);
|
||||||
|
|
||||||
final int pointer;
|
final int pointer;
|
||||||
final GestureArenaEntry entry;
|
final GestureArenaEntry entry;
|
||||||
final Offset _initialPosition;
|
final Offset _initialGlobalPosition;
|
||||||
final int initialButtons;
|
final int initialButtons;
|
||||||
final _CountdownZoned _doubleTapMinTimeCountdown;
|
final _CountdownZoned _doubleTapMinTimeCountdown;
|
||||||
|
|
||||||
bool _isTrackingPointer = false;
|
bool _isTrackingPointer = false;
|
||||||
|
|
||||||
void startTrackingPointer(PointerRoute route) {
|
void startTrackingPointer(PointerRoute route, Matrix4 transform) {
|
||||||
if (!_isTrackingPointer) {
|
if (!_isTrackingPointer) {
|
||||||
_isTrackingPointer = true;
|
_isTrackingPointer = true;
|
||||||
GestureBinding.instance.pointerRouter.addRoute(pointer, route);
|
GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +93,8 @@ class _TapTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWithinTolerance(PointerEvent event, double tolerance) {
|
bool isWithinGlobalTolerance(PointerEvent event, double tolerance) {
|
||||||
final Offset offset = event.position - _initialPosition;
|
final Offset offset = event.position - _initialGlobalPosition;
|
||||||
return offset.distance <= tolerance;
|
return offset.distance <= tolerance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +175,7 @@ class DoubleTapGestureRecognizer extends GestureRecognizer {
|
|||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerEvent event) {
|
void addAllowedPointer(PointerEvent event) {
|
||||||
if (_firstTap != null) {
|
if (_firstTap != null) {
|
||||||
if (!_firstTap.isWithinTolerance(event, kDoubleTapSlop)) {
|
if (!_firstTap.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
|
||||||
// Ignore out-of-bounds second taps.
|
// Ignore out-of-bounds second taps.
|
||||||
return;
|
return;
|
||||||
} else if (!_firstTap.hasElapsedMinTime() || !_firstTap.hasSameButton(event)) {
|
} else if (!_firstTap.hasElapsedMinTime() || !_firstTap.hasSameButton(event)) {
|
||||||
@ -195,7 +196,7 @@ class DoubleTapGestureRecognizer extends GestureRecognizer {
|
|||||||
doubleTapMinTime: kDoubleTapMinTime,
|
doubleTapMinTime: kDoubleTapMinTime,
|
||||||
);
|
);
|
||||||
_trackers[event.pointer] = tracker;
|
_trackers[event.pointer] = tracker;
|
||||||
tracker.startTrackingPointer(_handleEvent);
|
tracker.startTrackingPointer(_handleEvent, event.transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleEvent(PointerEvent event) {
|
void _handleEvent(PointerEvent event) {
|
||||||
@ -207,7 +208,7 @@ class DoubleTapGestureRecognizer extends GestureRecognizer {
|
|||||||
else
|
else
|
||||||
_registerSecondTap(tracker);
|
_registerSecondTap(tracker);
|
||||||
} else if (event is PointerMoveEvent) {
|
} else if (event is PointerMoveEvent) {
|
||||||
if (!tracker.isWithinTolerance(event, kDoubleTapTouchSlop))
|
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop))
|
||||||
_reject(tracker);
|
_reject(tracker);
|
||||||
} else if (event is PointerCancelEvent) {
|
} else if (event is PointerCancelEvent) {
|
||||||
_reject(tracker);
|
_reject(tracker);
|
||||||
@ -320,13 +321,13 @@ class _TapGesture extends _TapTracker {
|
|||||||
this.gestureRecognizer,
|
this.gestureRecognizer,
|
||||||
PointerEvent event,
|
PointerEvent event,
|
||||||
Duration longTapDelay,
|
Duration longTapDelay,
|
||||||
}) : _lastPosition = event.position,
|
}) : _lastPosition = OffsetPair.fromEventPosition(event),
|
||||||
super(
|
super(
|
||||||
event: event,
|
event: event,
|
||||||
entry: GestureBinding.instance.gestureArena.add(event.pointer, gestureRecognizer),
|
entry: GestureBinding.instance.gestureArena.add(event.pointer, gestureRecognizer),
|
||||||
doubleTapMinTime: kDoubleTapMinTime,
|
doubleTapMinTime: kDoubleTapMinTime,
|
||||||
) {
|
) {
|
||||||
startTrackingPointer(handleEvent);
|
startTrackingPointer(handleEvent, event.transform);
|
||||||
if (longTapDelay > Duration.zero) {
|
if (longTapDelay > Duration.zero) {
|
||||||
_timer = Timer(longTapDelay, () {
|
_timer = Timer(longTapDelay, () {
|
||||||
_timer = null;
|
_timer = null;
|
||||||
@ -340,21 +341,21 @@ class _TapGesture extends _TapTracker {
|
|||||||
bool _wonArena = false;
|
bool _wonArena = false;
|
||||||
Timer _timer;
|
Timer _timer;
|
||||||
|
|
||||||
Offset _lastPosition;
|
OffsetPair _lastPosition;
|
||||||
Offset _finalPosition;
|
OffsetPair _finalPosition;
|
||||||
|
|
||||||
void handleEvent(PointerEvent event) {
|
void handleEvent(PointerEvent event) {
|
||||||
assert(event.pointer == pointer);
|
assert(event.pointer == pointer);
|
||||||
if (event is PointerMoveEvent) {
|
if (event is PointerMoveEvent) {
|
||||||
if (!isWithinTolerance(event, kTouchSlop))
|
if (!isWithinGlobalTolerance(event, kTouchSlop))
|
||||||
cancel();
|
cancel();
|
||||||
else
|
else
|
||||||
_lastPosition = event.position;
|
_lastPosition = OffsetPair.fromEventPosition(event);
|
||||||
} else if (event is PointerCancelEvent) {
|
} else if (event is PointerCancelEvent) {
|
||||||
cancel();
|
cancel();
|
||||||
} else if (event is PointerUpEvent) {
|
} else if (event is PointerUpEvent) {
|
||||||
stopTrackingPointer(handleEvent);
|
stopTrackingPointer(handleEvent);
|
||||||
_finalPosition = event.position;
|
_finalPosition = OffsetPair.fromEventPosition(event);
|
||||||
_check();
|
_check();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,6 +448,7 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
|
|||||||
invokeCallback<void>('onTapDown', () {
|
invokeCallback<void>('onTapDown', () {
|
||||||
onTapDown(event.pointer, TapDownDetails(
|
onTapDown(event.pointer, TapDownDetails(
|
||||||
globalPosition: event.position,
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
kind: event.kind,
|
kind: event.kind,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
@ -472,23 +474,29 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
|
|||||||
invokeCallback<void>('onTapCancel', () => onTapCancel(pointer));
|
invokeCallback<void>('onTapCancel', () => onTapCancel(pointer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dispatchTap(int pointer, Offset globalPosition) {
|
void _dispatchTap(int pointer, OffsetPair position) {
|
||||||
assert(_gestureMap.containsKey(pointer));
|
assert(_gestureMap.containsKey(pointer));
|
||||||
_gestureMap.remove(pointer);
|
_gestureMap.remove(pointer);
|
||||||
if (onTapUp != null)
|
if (onTapUp != null)
|
||||||
invokeCallback<void>('onTapUp', () => onTapUp(pointer, TapUpDetails(globalPosition: globalPosition)));
|
invokeCallback<void>('onTapUp', () {
|
||||||
|
onTapUp(pointer, TapUpDetails(
|
||||||
|
localPosition: position.local,
|
||||||
|
globalPosition: position.global,
|
||||||
|
));
|
||||||
|
});
|
||||||
if (onTap != null)
|
if (onTap != null)
|
||||||
invokeCallback<void>('onTap', () => onTap(pointer));
|
invokeCallback<void>('onTap', () => onTap(pointer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dispatchLongTap(int pointer, Offset lastPosition) {
|
void _dispatchLongTap(int pointer, OffsetPair lastPosition) {
|
||||||
assert(_gestureMap.containsKey(pointer));
|
assert(_gestureMap.containsKey(pointer));
|
||||||
if (onLongTapDown != null)
|
if (onLongTapDown != null)
|
||||||
invokeCallback<void>('onLongTapDown', () {
|
invokeCallback<void>('onLongTapDown', () {
|
||||||
onLongTapDown(
|
onLongTapDown(
|
||||||
pointer,
|
pointer,
|
||||||
TapDownDetails(
|
TapDownDetails(
|
||||||
globalPosition: lastPosition,
|
globalPosition: lastPosition.global,
|
||||||
|
localPosition: lastPosition.local,
|
||||||
kind: getKindForPointer(pointer),
|
kind: getKindForPointer(pointer),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import 'events.dart';
|
import 'events.dart';
|
||||||
|
|
||||||
@ -13,8 +14,8 @@ typedef PointerRoute = void Function(PointerEvent event);
|
|||||||
|
|
||||||
/// A routing table for [PointerEvent] events.
|
/// A routing table for [PointerEvent] events.
|
||||||
class PointerRouter {
|
class PointerRouter {
|
||||||
final Map<int, LinkedHashSet<PointerRoute>> _routeMap = <int, LinkedHashSet<PointerRoute>>{};
|
final Map<int, LinkedHashSet<_RouteEntry>> _routeMap = <int, LinkedHashSet<_RouteEntry>>{};
|
||||||
final LinkedHashSet<PointerRoute> _globalRoutes = LinkedHashSet<PointerRoute>();
|
final LinkedHashSet<_RouteEntry> _globalRoutes = LinkedHashSet<_RouteEntry>();
|
||||||
|
|
||||||
/// Adds a route to the routing table.
|
/// Adds a route to the routing table.
|
||||||
///
|
///
|
||||||
@ -23,10 +24,10 @@ class PointerRouter {
|
|||||||
///
|
///
|
||||||
/// Routes added reentrantly within [PointerRouter.route] will take effect when
|
/// Routes added reentrantly within [PointerRouter.route] will take effect when
|
||||||
/// routing the next event.
|
/// routing the next event.
|
||||||
void addRoute(int pointer, PointerRoute route) {
|
void addRoute(int pointer, PointerRoute route, [Matrix4 transform]) {
|
||||||
final LinkedHashSet<PointerRoute> routes = _routeMap.putIfAbsent(pointer, () => LinkedHashSet<PointerRoute>());
|
final LinkedHashSet<_RouteEntry> routes = _routeMap.putIfAbsent(pointer, () => LinkedHashSet<_RouteEntry>());
|
||||||
assert(!routes.contains(route));
|
assert(!routes.any(_RouteEntry.isRoutePredicate(route)));
|
||||||
routes.add(route);
|
routes.add(_RouteEntry(route: route, transform: transform));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a route from the routing table.
|
/// Removes a route from the routing table.
|
||||||
@ -38,9 +39,9 @@ class PointerRouter {
|
|||||||
/// immediately.
|
/// immediately.
|
||||||
void removeRoute(int pointer, PointerRoute route) {
|
void removeRoute(int pointer, PointerRoute route) {
|
||||||
assert(_routeMap.containsKey(pointer));
|
assert(_routeMap.containsKey(pointer));
|
||||||
final LinkedHashSet<PointerRoute> routes = _routeMap[pointer];
|
final LinkedHashSet<_RouteEntry> routes = _routeMap[pointer];
|
||||||
assert(routes.contains(route));
|
assert(routes.any(_RouteEntry.isRoutePredicate(route)));
|
||||||
routes.remove(route);
|
routes.removeWhere(_RouteEntry.isRoutePredicate(route));
|
||||||
if (routes.isEmpty)
|
if (routes.isEmpty)
|
||||||
_routeMap.remove(pointer);
|
_routeMap.remove(pointer);
|
||||||
}
|
}
|
||||||
@ -51,9 +52,9 @@ class PointerRouter {
|
|||||||
///
|
///
|
||||||
/// Routes added reentrantly within [PointerRouter.route] will take effect when
|
/// Routes added reentrantly within [PointerRouter.route] will take effect when
|
||||||
/// routing the next event.
|
/// routing the next event.
|
||||||
void addGlobalRoute(PointerRoute route) {
|
void addGlobalRoute(PointerRoute route, [Matrix4 transform]) {
|
||||||
assert(!_globalRoutes.contains(route));
|
assert(!_globalRoutes.any(_RouteEntry.isRoutePredicate(route)));
|
||||||
_globalRoutes.add(route);
|
_globalRoutes.add(_RouteEntry(route: route, transform: transform));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a route from the global entry in the routing table.
|
/// Removes a route from the global entry in the routing table.
|
||||||
@ -64,13 +65,14 @@ class PointerRouter {
|
|||||||
/// Routes removed reentrantly within [PointerRouter.route] will take effect
|
/// Routes removed reentrantly within [PointerRouter.route] will take effect
|
||||||
/// immediately.
|
/// immediately.
|
||||||
void removeGlobalRoute(PointerRoute route) {
|
void removeGlobalRoute(PointerRoute route) {
|
||||||
assert(_globalRoutes.contains(route));
|
assert(_globalRoutes.any(_RouteEntry.isRoutePredicate(route)));
|
||||||
_globalRoutes.remove(route);
|
_globalRoutes.removeWhere(_RouteEntry.isRoutePredicate(route));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dispatch(PointerEvent event, PointerRoute route) {
|
void _dispatch(PointerEvent event, _RouteEntry entry) {
|
||||||
try {
|
try {
|
||||||
route(event);
|
event = event.transformed(entry.transform);
|
||||||
|
entry.route(event);
|
||||||
} catch (exception, stack) {
|
} catch (exception, stack) {
|
||||||
FlutterError.reportError(FlutterErrorDetailsForPointerRouter(
|
FlutterError.reportError(FlutterErrorDetailsForPointerRouter(
|
||||||
exception: exception,
|
exception: exception,
|
||||||
@ -78,7 +80,7 @@ class PointerRouter {
|
|||||||
library: 'gesture library',
|
library: 'gesture library',
|
||||||
context: ErrorDescription('while routing a pointer event'),
|
context: ErrorDescription('while routing a pointer event'),
|
||||||
router: this,
|
router: this,
|
||||||
route: route,
|
route: entry.route,
|
||||||
event: event,
|
event: event,
|
||||||
informationCollector: () sync* {
|
informationCollector: () sync* {
|
||||||
yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
|
yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
|
||||||
@ -92,17 +94,17 @@ class PointerRouter {
|
|||||||
/// Routes are called in the order in which they were added to the
|
/// Routes are called in the order in which they were added to the
|
||||||
/// PointerRouter object.
|
/// PointerRouter object.
|
||||||
void route(PointerEvent event) {
|
void route(PointerEvent event) {
|
||||||
final LinkedHashSet<PointerRoute> routes = _routeMap[event.pointer];
|
final LinkedHashSet<_RouteEntry> routes = _routeMap[event.pointer];
|
||||||
final List<PointerRoute> globalRoutes = List<PointerRoute>.from(_globalRoutes);
|
final List<_RouteEntry> globalRoutes = List<_RouteEntry>.from(_globalRoutes);
|
||||||
if (routes != null) {
|
if (routes != null) {
|
||||||
for (PointerRoute route in List<PointerRoute>.from(routes)) {
|
for (_RouteEntry entry in List<_RouteEntry>.from(routes)) {
|
||||||
if (routes.contains(route))
|
if (routes.any(_RouteEntry.isRoutePredicate(entry.route)))
|
||||||
_dispatch(event, route);
|
_dispatch(event, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (PointerRoute route in globalRoutes) {
|
for (_RouteEntry entry in globalRoutes) {
|
||||||
if (_globalRoutes.contains(route))
|
if (_globalRoutes.any(_RouteEntry.isRoutePredicate(entry.route)))
|
||||||
_dispatch(event, route);
|
_dispatch(event, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,3 +151,19 @@ class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails {
|
|||||||
/// The pointer event that was being routed when the exception was raised.
|
/// The pointer event that was being routed when the exception was raised.
|
||||||
final PointerEvent event;
|
final PointerEvent event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef _RouteEntryPredicate = bool Function(_RouteEntry entry);
|
||||||
|
|
||||||
|
class _RouteEntry {
|
||||||
|
const _RouteEntry({
|
||||||
|
@required this.route,
|
||||||
|
@required this.transform,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PointerRoute route;
|
||||||
|
final Matrix4 transform;
|
||||||
|
|
||||||
|
static _RouteEntryPredicate isRoutePredicate(PointerRoute route) {
|
||||||
|
return (_RouteEntry entry) => entry.route == route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -47,9 +47,9 @@ class PointerSignalResolver {
|
|||||||
assert(_currentEvent == null);
|
assert(_currentEvent == null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(_currentEvent == event);
|
assert((_currentEvent.original ?? _currentEvent) == event);
|
||||||
try {
|
try {
|
||||||
_firstRegisteredCallback(event);
|
_firstRegisteredCallback(_currentEvent);
|
||||||
} catch (exception, stack) {
|
} catch (exception, stack) {
|
||||||
FlutterError.reportError(FlutterErrorDetails(
|
FlutterError.reportError(FlutterErrorDetails(
|
||||||
exception: exception,
|
exception: exception,
|
||||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:ui' show Offset;
|
import 'dart:ui' show Offset;
|
||||||
|
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'arena.dart';
|
import 'arena.dart';
|
||||||
@ -293,12 +294,16 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
|
|||||||
|
|
||||||
/// Causes events related to the given pointer ID to be routed to this recognizer.
|
/// Causes events related to the given pointer ID to be routed to this recognizer.
|
||||||
///
|
///
|
||||||
/// The pointer events are delivered to [handleEvent].
|
/// The pointer events are transformed according to `transform` and then delivered
|
||||||
|
/// to [handleEvent]. The value for the `transform` argument is usually obtained
|
||||||
|
/// from [PointerDownEvent.transform] to transform the events from the global
|
||||||
|
/// coordinate space into the coordinate space of the event receiver. It may be
|
||||||
|
/// null if no transformation is necessary.
|
||||||
///
|
///
|
||||||
/// Use [stopTrackingPointer] to remove the route added by this function.
|
/// Use [stopTrackingPointer] to remove the route added by this function.
|
||||||
@protected
|
@protected
|
||||||
void startTrackingPointer(int pointer) {
|
void startTrackingPointer(int pointer, [Matrix4 transform]) {
|
||||||
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
|
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
|
||||||
_trackedPointers.add(pointer);
|
_trackedPointers.add(pointer);
|
||||||
assert(!_entries.containsValue(pointer));
|
assert(!_entries.containsValue(pointer));
|
||||||
_entries[pointer] = _addPointerToArena(pointer);
|
_entries[pointer] = _addPointerToArena(pointer);
|
||||||
@ -410,8 +415,8 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
|||||||
/// The ID of the primary pointer this recognizer is tracking.
|
/// The ID of the primary pointer this recognizer is tracking.
|
||||||
int primaryPointer;
|
int primaryPointer;
|
||||||
|
|
||||||
/// The global location at which the primary pointer contacted the screen.
|
/// The location at which the primary pointer contacted the screen.
|
||||||
Offset initialPosition;
|
OffsetPair initialPosition;
|
||||||
|
|
||||||
// Whether this pointer is accepted by winning the arena or as defined by
|
// Whether this pointer is accepted by winning the arena or as defined by
|
||||||
// a subclass calling acceptGesture.
|
// a subclass calling acceptGesture.
|
||||||
@ -420,11 +425,11 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerDownEvent event) {
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
startTrackingPointer(event.pointer);
|
startTrackingPointer(event.pointer, event.transform);
|
||||||
if (state == GestureRecognizerState.ready) {
|
if (state == GestureRecognizerState.ready) {
|
||||||
state = GestureRecognizerState.possible;
|
state = GestureRecognizerState.possible;
|
||||||
primaryPointer = event.pointer;
|
primaryPointer = event.pointer;
|
||||||
initialPosition = event.position;
|
initialPosition = OffsetPair(local: event.localPosition, global: event.position);
|
||||||
if (deadline != null)
|
if (deadline != null)
|
||||||
_timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
|
_timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
|
||||||
}
|
}
|
||||||
@ -437,11 +442,11 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
|||||||
final bool isPreAcceptSlopPastTolerance =
|
final bool isPreAcceptSlopPastTolerance =
|
||||||
!_gestureAccepted &&
|
!_gestureAccepted &&
|
||||||
preAcceptSlopTolerance != null &&
|
preAcceptSlopTolerance != null &&
|
||||||
_getDistance(event) > preAcceptSlopTolerance;
|
_getGlobalDistance(event) > preAcceptSlopTolerance;
|
||||||
final bool isPostAcceptSlopPastTolerance =
|
final bool isPostAcceptSlopPastTolerance =
|
||||||
_gestureAccepted &&
|
_gestureAccepted &&
|
||||||
postAcceptSlopTolerance != null &&
|
postAcceptSlopTolerance != null &&
|
||||||
_getDistance(event) > postAcceptSlopTolerance;
|
_getGlobalDistance(event) > postAcceptSlopTolerance;
|
||||||
|
|
||||||
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
|
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
|
||||||
resolve(GestureDisposition.rejected);
|
resolve(GestureDisposition.rejected);
|
||||||
@ -509,8 +514,8 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double _getDistance(PointerEvent event) {
|
double _getGlobalDistance(PointerEvent event) {
|
||||||
final Offset offset = event.position - initialPosition;
|
final Offset offset = event.position - initialPosition.global;
|
||||||
return offset.distance;
|
return offset.distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,3 +525,57 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
|||||||
properties.add(EnumProperty<GestureRecognizerState>('state', state));
|
properties.add(EnumProperty<GestureRecognizerState>('state', state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A container for a [local] and [global] [Offset] pair.
|
||||||
|
///
|
||||||
|
/// Usually, the [global] [Offset] is in the coordinate space of the screen
|
||||||
|
/// after conversion to logical pixels and the [local] offset is the same
|
||||||
|
/// [Offset], but transformed to a local coordinate space.
|
||||||
|
class OffsetPair {
|
||||||
|
/// Creates a [OffsetPair] combining a [local] and [global] [Offset].
|
||||||
|
const OffsetPair({
|
||||||
|
@required this.local,
|
||||||
|
@required this.global,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Creates a [OffsetPair] from [PointerEvent.localPosition] and
|
||||||
|
/// [PointerEvent.position].
|
||||||
|
factory OffsetPair.fromEventPosition(PointerEvent event) {
|
||||||
|
return OffsetPair(local: event.localPosition, global: event.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [OffsetPair] from [PointerEvent.localDelta] and
|
||||||
|
/// [PointerEvent.delta].
|
||||||
|
factory OffsetPair.fromEventDelta(PointerEvent event) {
|
||||||
|
return OffsetPair(local: event.localDelta, global: event.delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [OffsetPair] where both [Offset]s are [Offset.zero].
|
||||||
|
static const OffsetPair zero = OffsetPair(local: Offset.zero, global: Offset.zero);
|
||||||
|
|
||||||
|
/// The [Offset] in the local coordinate space.
|
||||||
|
final Offset local;
|
||||||
|
|
||||||
|
/// The [Offset] in the global coordinate space after conversion to logical
|
||||||
|
/// pixels.
|
||||||
|
final Offset global;
|
||||||
|
|
||||||
|
/// Adds the `other.global` to [global] and `other.local` to [local].
|
||||||
|
OffsetPair operator+(OffsetPair other) {
|
||||||
|
return OffsetPair(
|
||||||
|
local: local + other.local,
|
||||||
|
global: global + other.global,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts the `other.global` from [global] and `other.local` from [local].
|
||||||
|
OffsetPair operator-(OffsetPair other) {
|
||||||
|
return OffsetPair(
|
||||||
|
local: local - other.local,
|
||||||
|
global: global - other.global,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$runtimeType(local: $local, global: $global)';
|
||||||
|
}
|
||||||
|
@ -21,14 +21,19 @@ class TapDownDetails {
|
|||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
TapDownDetails({
|
TapDownDetails({
|
||||||
this.globalPosition = Offset.zero,
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
this.kind,
|
this.kind,
|
||||||
}) : assert(globalPosition != null);
|
}) : assert(globalPosition != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// The global position at which the pointer contacted the screen.
|
/// The global position at which the pointer contacted the screen.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
/// The kind of the device that initiated the event.
|
/// The kind of the device that initiated the event.
|
||||||
final PointerDeviceKind kind;
|
final PointerDeviceKind kind;
|
||||||
|
|
||||||
|
/// The local position at which the pointer contacted the screen.
|
||||||
|
final Offset localPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signature for when a pointer that might cause a tap has contacted the
|
/// Signature for when a pointer that might cause a tap has contacted the
|
||||||
@ -51,11 +56,17 @@ typedef GestureTapDownCallback = void Function(TapDownDetails details);
|
|||||||
/// * [TapGestureRecognizer], which passes this information to one of its callbacks.
|
/// * [TapGestureRecognizer], which passes this information to one of its callbacks.
|
||||||
class TapUpDetails {
|
class TapUpDetails {
|
||||||
/// The [globalPosition] argument must not be null.
|
/// The [globalPosition] argument must not be null.
|
||||||
TapUpDetails({ this.globalPosition = Offset.zero })
|
TapUpDetails({
|
||||||
: assert(globalPosition != null);
|
this.globalPosition = Offset.zero,
|
||||||
|
Offset localPosition,
|
||||||
|
}) : assert(globalPosition != null),
|
||||||
|
localPosition = localPosition ?? globalPosition;
|
||||||
|
|
||||||
/// The global position at which the pointer contacted the screen.
|
/// The global position at which the pointer contacted the screen.
|
||||||
final Offset globalPosition;
|
final Offset globalPosition;
|
||||||
|
|
||||||
|
/// The local position at which the pointer contacted the screen.
|
||||||
|
final Offset localPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signature for when a pointer that will trigger a tap has stopped contacting
|
/// Signature for when a pointer that will trigger a tap has stopped contacting
|
||||||
@ -221,7 +232,7 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
|
|
||||||
bool _sentTapDown = false;
|
bool _sentTapDown = false;
|
||||||
bool _wonArenaForPrimaryPointer = false;
|
bool _wonArenaForPrimaryPointer = false;
|
||||||
Offset _finalPosition;
|
OffsetPair _finalPosition;
|
||||||
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
|
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
|
||||||
// different set of buttons, the gesture is canceled.
|
// different set of buttons, the gesture is canceled.
|
||||||
int _initialButtons;
|
int _initialButtons;
|
||||||
@ -260,7 +271,7 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
@override
|
@override
|
||||||
void handlePrimaryPointer(PointerEvent event) {
|
void handlePrimaryPointer(PointerEvent event) {
|
||||||
if (event is PointerUpEvent) {
|
if (event is PointerUpEvent) {
|
||||||
_finalPosition = event.position;
|
_finalPosition = OffsetPair(global: event.position, local: event.localPosition);
|
||||||
_checkUp();
|
_checkUp();
|
||||||
} else if (event is PointerCancelEvent) {
|
} else if (event is PointerCancelEvent) {
|
||||||
resolve(GestureDisposition.rejected);
|
resolve(GestureDisposition.rejected);
|
||||||
@ -319,7 +330,8 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final TapDownDetails details = TapDownDetails(
|
final TapDownDetails details = TapDownDetails(
|
||||||
globalPosition: initialPosition,
|
globalPosition: initialPosition.global,
|
||||||
|
localPosition: initialPosition.local,
|
||||||
kind: getKindForPointer(pointer),
|
kind: getKindForPointer(pointer),
|
||||||
);
|
);
|
||||||
switch (_initialButtons) {
|
switch (_initialButtons) {
|
||||||
@ -342,7 +354,8 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final TapUpDetails details = TapUpDetails(
|
final TapUpDetails details = TapUpDetails(
|
||||||
globalPosition: _finalPosition,
|
globalPosition: _finalPosition.global,
|
||||||
|
localPosition: _finalPosition.local,
|
||||||
);
|
);
|
||||||
switch (_initialButtons) {
|
switch (_initialButtons) {
|
||||||
case kPrimaryButton:
|
case kPrimaryButton:
|
||||||
@ -390,7 +403,8 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
|
properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
|
||||||
properties.add(DiagnosticsProperty<Offset>('finalPosition', _finalPosition, defaultValue: null));
|
properties.add(DiagnosticsProperty<Offset>('finalPosition', _finalPosition?.global, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<Offset>('finalLocalPosition', _finalPosition?.local, defaultValue: _finalPosition?.global));
|
||||||
properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
|
properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
|
||||||
// TODO(tongmu): Add property _initialButtons and update related tests
|
// TODO(tongmu): Add property _initialButtons and update related tests
|
||||||
}
|
}
|
||||||
|
@ -657,8 +657,10 @@ class BoxHitTestResult extends HitTestResult {
|
|||||||
/// provided `hitTest` callback, which is invoked with the transformed
|
/// provided `hitTest` callback, which is invoked with the transformed
|
||||||
/// `position` as argument.
|
/// `position` as argument.
|
||||||
///
|
///
|
||||||
/// Since the provided paint `transform` describes the transform from the
|
/// The provided paint `transform` (which describes the transform from the
|
||||||
/// child to the parent, the matrix is inverted before it is used to transform
|
/// child to the parent in 3D) is processed by
|
||||||
|
/// [PointerEvent.removePerspectiveTransform] to remove the
|
||||||
|
/// perspective component and inverted before it is used to transform
|
||||||
/// `position` from the coordinate system of the parent to the system of the
|
/// `position` from the coordinate system of the parent to the system of the
|
||||||
/// child.
|
/// child.
|
||||||
///
|
///
|
||||||
@ -674,7 +676,8 @@ class BoxHitTestResult extends HitTestResult {
|
|||||||
/// position is not required to do the actual hit testing in that protocol.
|
/// position is not required to do the actual hit testing in that protocol.
|
||||||
///
|
///
|
||||||
/// {@tool sample}
|
/// {@tool sample}
|
||||||
/// This method is used in [RenderBox.hitTestChildren]:
|
/// This method is used in [RenderBox.hitTestChildren] when the child and
|
||||||
|
/// parent don't share the same origin.
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// abstract class Foo extends RenderBox {
|
/// abstract class Foo extends RenderBox {
|
||||||
@ -713,7 +716,7 @@ class BoxHitTestResult extends HitTestResult {
|
|||||||
}) {
|
}) {
|
||||||
assert(hitTest != null);
|
assert(hitTest != null);
|
||||||
if (transform != null) {
|
if (transform != null) {
|
||||||
transform = Matrix4.tryInvert(transform);
|
transform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
|
||||||
if (transform == null) {
|
if (transform == null) {
|
||||||
// Objects are not visible on screen and cannot be hit-tested.
|
// Objects are not visible on screen and cannot be hit-tested.
|
||||||
return false;
|
return false;
|
||||||
@ -788,7 +791,14 @@ class BoxHitTestResult extends HitTestResult {
|
|||||||
final Offset transformedPosition = position == null || transform == null
|
final Offset transformedPosition = position == null || transform == null
|
||||||
? position
|
? position
|
||||||
: MatrixUtils.transformPoint(transform, position);
|
: MatrixUtils.transformPoint(transform, position);
|
||||||
return hitTest(this, transformedPosition);
|
if (transform != null) {
|
||||||
|
pushTransform(transform);
|
||||||
|
}
|
||||||
|
final bool isHit = hitTest(this, transformedPosition);
|
||||||
|
if (transform != null) {
|
||||||
|
popTransform();
|
||||||
|
}
|
||||||
|
return isHit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,7 +807,7 @@ class BoxHitTestEntry extends HitTestEntry {
|
|||||||
/// Creates a box hit test entry.
|
/// Creates a box hit test entry.
|
||||||
///
|
///
|
||||||
/// The [localPosition] argument must not be null.
|
/// The [localPosition] argument must not be null.
|
||||||
const BoxHitTestEntry(RenderBox target, this.localPosition)
|
BoxHitTestEntry(RenderBox target, this.localPosition)
|
||||||
: assert(localPosition != null),
|
: assert(localPosition != null),
|
||||||
super(target);
|
super(target);
|
||||||
|
|
||||||
@ -2057,10 +2067,11 @@ abstract class RenderBox extends RenderObject {
|
|||||||
/// This [RenderBox] is responsible for checking whether the given position is
|
/// This [RenderBox] is responsible for checking whether the given position is
|
||||||
/// within its bounds.
|
/// within its bounds.
|
||||||
///
|
///
|
||||||
/// If transforming is necessary, [BoxHitTestResult.addWithPaintTransform],
|
/// If transforming is necessary, [HitTestResult.addWithPaintTransform],
|
||||||
/// [BoxHitTestResult.addWithPaintOffset], or
|
/// [HitTestResult.addWithPaintOffset], or [HitTestResult.addWithRawTransform] need
|
||||||
/// [BoxHitTestResult.addWithRawTransform] should be used to transform
|
/// to be invoked by the caller to record the required transform operations
|
||||||
/// `position` to the local coordinate system.
|
/// in the [HitTestResult]. These methods will also help with applying the
|
||||||
|
/// transform to `position`.
|
||||||
///
|
///
|
||||||
/// Hit testing requires layout to be up-to-date but does not require painting
|
/// Hit testing requires layout to be up-to-date but does not require painting
|
||||||
/// to be up-to-date. That means a render object can rely upon [performLayout]
|
/// to be up-to-date. That means a render object can rely upon [performLayout]
|
||||||
@ -2130,10 +2141,11 @@ abstract class RenderBox extends RenderObject {
|
|||||||
/// This [RenderBox] is responsible for checking whether the given position is
|
/// This [RenderBox] is responsible for checking whether the given position is
|
||||||
/// within its bounds.
|
/// within its bounds.
|
||||||
///
|
///
|
||||||
/// If transforming is necessary, [BoxHitTestResult.addWithPaintTransform],
|
/// If transforming is necessary, [HitTestResult.addWithPaintTransform],
|
||||||
/// [BoxHitTestResult.addWithPaintOffset], or
|
/// [HitTestResult.addWithPaintOffset], or [HitTestResult.addWithRawTransform] need
|
||||||
/// [BoxHitTestResult.addWithRawTransform] should be used to transform
|
/// to be invoked by the caller to record the required transform operations
|
||||||
/// `position` to the local coordinate system.
|
/// in the [HitTestResult]. These methods will also help with applying the
|
||||||
|
/// transform to `position`.
|
||||||
///
|
///
|
||||||
/// Used by [hitTest]. If you override [hitTest] and do not call this
|
/// Used by [hitTest]. If you override [hitTest] and do not call this
|
||||||
/// function, then you don't need to implement this function.
|
/// function, then you don't need to implement this function.
|
||||||
|
@ -8,6 +8,7 @@ import 'dart:ui' as ui show EngineLayer, Image, ImageFilter, PathMetric,
|
|||||||
Picture, PictureRecorder, Scene, SceneBuilder;
|
Picture, PictureRecorder, Scene, SceneBuilder;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
@ -1237,7 +1238,9 @@ class TransformLayer extends OffsetLayer {
|
|||||||
|
|
||||||
Offset _transformOffset(Offset regionOffset) {
|
Offset _transformOffset(Offset regionOffset) {
|
||||||
if (_inverseDirty) {
|
if (_inverseDirty) {
|
||||||
_invertedTransform = Matrix4.tryInvert(transform);
|
_invertedTransform = Matrix4.tryInvert(
|
||||||
|
PointerEvent.removePerspectiveTransform(transform)
|
||||||
|
);
|
||||||
_inverseDirty = false;
|
_inverseDirty = false;
|
||||||
}
|
}
|
||||||
if (_invertedTransform == null)
|
if (_invertedTransform == null)
|
||||||
|
@ -984,9 +984,7 @@ class RenderListWheelViewport
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
|
bool hitTestChildren(BoxHitTestResult result, { Offset position }) => false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect rect }) {
|
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect rect }) {
|
||||||
|
@ -381,7 +381,7 @@ class RenderUiKitView extends RenderBox {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_gestureRecognizer.addPointer(event);
|
_gestureRecognizer.addPointer(event);
|
||||||
_lastPointerDownEvent = event;
|
_lastPointerDownEvent = event.original ?? event;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is registered as a global PointerRoute while the render object is attached.
|
// This is registered as a global PointerRoute while the render object is attached.
|
||||||
@ -389,11 +389,10 @@ class RenderUiKitView extends RenderBox {
|
|||||||
if (event is! PointerDownEvent) {
|
if (event is! PointerDownEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Offset localOffset = globalToLocal(event.position);
|
if (!(Offset.zero & size).contains(event.localPosition)) {
|
||||||
if (!(Offset.zero & size).contains(localOffset)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event != _lastPointerDownEvent) {
|
if ((event.original ?? event) != _lastPointerDownEvent) {
|
||||||
// The pointer event is in the bounds of this render box, but we didn't get it in handleEvent.
|
// The pointer event is in the bounds of this render box, but we didn't get it in handleEvent.
|
||||||
// This means that the pointer event was absorbed by a different render object.
|
// This means that the pointer event was absorbed by a different render object.
|
||||||
// Since on the platform side the FlutterTouchIntercepting view is seeing all events that are
|
// Since on the platform side the FlutterTouchIntercepting view is seeing all events that are
|
||||||
@ -455,7 +454,7 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerDownEvent event) {
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
startTrackingPointer(event.pointer);
|
startTrackingPointer(event.pointer, event.transform);
|
||||||
for (OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
|
for (OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
|
||||||
recognizer.addPointer(event);
|
recognizer.addPointer(event);
|
||||||
}
|
}
|
||||||
@ -528,7 +527,7 @@ class _AndroidViewGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerDownEvent event) {
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
startTrackingPointer(event.pointer);
|
startTrackingPointer(event.pointer, event.transform);
|
||||||
for (OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
|
for (OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
|
||||||
recognizer.addPointer(event);
|
recognizer.addPointer(event);
|
||||||
}
|
}
|
||||||
|
@ -846,12 +846,18 @@ class SliverHitTestResult extends HitTestResult {
|
|||||||
assert(mainAxisPosition != null);
|
assert(mainAxisPosition != null);
|
||||||
assert(crossAxisPosition != null);
|
assert(crossAxisPosition != null);
|
||||||
assert(hitTest != null);
|
assert(hitTest != null);
|
||||||
// TODO(goderbauer): use paintOffset when transforming pointer events is implemented.
|
if (paintOffset != null) {
|
||||||
return hitTest(
|
pushTransform(Matrix4.translationValues(paintOffset.dx, paintOffset.dy, 0));
|
||||||
|
}
|
||||||
|
final bool isHit = hitTest(
|
||||||
this,
|
this,
|
||||||
mainAxisPosition: mainAxisPosition - mainAxisOffset,
|
mainAxisPosition: mainAxisPosition - mainAxisOffset,
|
||||||
crossAxisPosition: crossAxisPosition - crossAxisOffset,
|
crossAxisPosition: crossAxisPosition - crossAxisOffset,
|
||||||
);
|
);
|
||||||
|
if (paintOffset != null) {
|
||||||
|
popTransform();
|
||||||
|
}
|
||||||
|
return isHit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -863,7 +869,7 @@ class SliverHitTestEntry extends HitTestEntry {
|
|||||||
/// Creates a sliver hit test entry.
|
/// Creates a sliver hit test entry.
|
||||||
///
|
///
|
||||||
/// The [mainAxisPosition] and [crossAxisPosition] arguments must not be null.
|
/// The [mainAxisPosition] and [crossAxisPosition] arguments must not be null.
|
||||||
const SliverHitTestEntry(
|
SliverHitTestEntry(
|
||||||
RenderSliver target, {
|
RenderSliver target, {
|
||||||
@required this.mainAxisPosition,
|
@required this.mainAxisPosition,
|
||||||
@required this.crossAxisPosition,
|
@required this.crossAxisPosition,
|
||||||
|
@ -66,9 +66,7 @@ class TextureBox extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool hitTestSelf(Offset position) {
|
bool hitTestSelf(Offset position) => true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
@ -663,7 +663,7 @@ class _DragAvatar<T> extends Drag {
|
|||||||
_activeTarget = newTarget;
|
_activeTarget = newTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<_DragTargetState<T>> _getDragTargets(List<HitTestEntry> path) sync* {
|
Iterable<_DragTargetState<T>> _getDragTargets(Iterable<HitTestEntry> path) sync* {
|
||||||
// Look for the RenderBoxes that corresponds to the hit target (the hit target
|
// Look for the RenderBoxes that corresponds to the hit target (the hit target
|
||||||
// widgets build RenderMetaData boxes for us for this purpose).
|
// widgets build RenderMetaData boxes for us for this purpose).
|
||||||
for (HitTestEntry entry in path) {
|
for (HitTestEntry entry in path) {
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import 'gesture_tester.dart';
|
import 'gesture_tester.dart';
|
||||||
|
|
||||||
@ -124,4 +126,344 @@ void main() {
|
|||||||
expect(event.buttons, kPrimaryButton);
|
expect(event.buttons, kPrimaryButton);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('paintTransformToPointerEventTransform', () {
|
||||||
|
Matrix4 original = Matrix4.identity();
|
||||||
|
Matrix4 changed = PointerEvent.removePerspectiveTransform(original);
|
||||||
|
expect(changed, original);
|
||||||
|
|
||||||
|
original = Matrix4.identity()..scale(3.0);
|
||||||
|
changed = PointerEvent.removePerspectiveTransform(original);
|
||||||
|
expect(changed, isNot(original));
|
||||||
|
original
|
||||||
|
..setColumn(2, Vector4(0, 0, 1, 0))
|
||||||
|
..setRow(2, Vector4(0, 0, 1, 0));
|
||||||
|
expect(changed, original);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('transformPosition', () {
|
||||||
|
const Offset position = Offset(20, 30);
|
||||||
|
expect(PointerEvent.transformPosition(null, position), position);
|
||||||
|
expect(PointerEvent.transformPosition(Matrix4.identity(), position), position);
|
||||||
|
final Matrix4 transform = Matrix4.translationValues(10, 20, 0);
|
||||||
|
expect(PointerEvent.transformPosition(transform, position), const Offset(20.0 + 10.0, 30.0 + 20.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('transformDeltaViaPositions', () {
|
||||||
|
Offset transformedDelta = PointerEvent.transformDeltaViaPositions(
|
||||||
|
untransformedEndPosition: const Offset(20, 30),
|
||||||
|
untransformedDelta: const Offset(5, 5),
|
||||||
|
transform: Matrix4.identity()..scale(2.0, 2.0, 1.0),
|
||||||
|
);
|
||||||
|
expect(transformedDelta, const Offset(10.0, 10.0));
|
||||||
|
|
||||||
|
transformedDelta = PointerEvent.transformDeltaViaPositions(
|
||||||
|
untransformedEndPosition: const Offset(20, 30),
|
||||||
|
transformedEndPosition: const Offset(40, 60),
|
||||||
|
untransformedDelta: const Offset(5, 5),
|
||||||
|
transform: Matrix4.identity()..scale(2.0, 2.0, 1.0),
|
||||||
|
);
|
||||||
|
expect(transformedDelta, const Offset(10.0, 10.0));
|
||||||
|
|
||||||
|
transformedDelta = PointerEvent.transformDeltaViaPositions(
|
||||||
|
untransformedEndPosition: const Offset(20, 30),
|
||||||
|
transformedEndPosition: const Offset(40, 60),
|
||||||
|
untransformedDelta: const Offset(5, 5),
|
||||||
|
transform: null,
|
||||||
|
);
|
||||||
|
expect(transformedDelta, const Offset(5, 5));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('transforming events', () {
|
||||||
|
final Matrix4 transform = (Matrix4.identity()..scale(2.0, 2.0, 1.0)) * Matrix4.translationValues(10.0, 20.0, 0.0);
|
||||||
|
const Offset localPosition = Offset(60, 100);
|
||||||
|
const Offset localDelta = Offset(10, 10);
|
||||||
|
|
||||||
|
const PointerAddedEvent added = PointerAddedEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
obscured: true,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distance: 12,
|
||||||
|
distanceMax: 24,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: added,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerCancelEvent cancel = PointerCancelEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
pointer: 45,
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distance: 12,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: cancel,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerDownEvent down = PointerDownEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
pointer: 45,
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressure: 34,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: down,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerEnterEvent enter = PointerEnterEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
delta: Offset(5, 5),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distance: 12,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: enter,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
localDelta: localDelta,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerExitEvent exit = PointerExitEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
delta: Offset(5, 5),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distance: 12,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: exit,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
localDelta: localDelta,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerHoverEvent hover = PointerHoverEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
delta: Offset(5, 5),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distance: 12,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: hover,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
localDelta: localDelta,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerMoveEvent move = PointerMoveEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
pointer: 45,
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
delta: Offset(5, 5),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressure: 34,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
platformData: 10,
|
||||||
|
synthesized: true,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: move,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
localDelta: localDelta,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerRemovedEvent removed = PointerRemovedEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
obscured: true,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distanceMax: 24,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: removed,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerScrollEvent scroll = PointerScrollEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: scroll,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PointerUpEvent up = PointerUpEvent(
|
||||||
|
timeStamp: Duration(seconds: 2),
|
||||||
|
pointer: 45,
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
device: 1,
|
||||||
|
position: Offset(20, 30),
|
||||||
|
buttons: 4,
|
||||||
|
obscured: true,
|
||||||
|
pressure: 34,
|
||||||
|
pressureMin: 10,
|
||||||
|
pressureMax: 60,
|
||||||
|
distance: 12,
|
||||||
|
distanceMax: 24,
|
||||||
|
size: 10,
|
||||||
|
radiusMajor: 33,
|
||||||
|
radiusMinor: 44,
|
||||||
|
radiusMin: 10,
|
||||||
|
radiusMax: 50,
|
||||||
|
orientation: 2,
|
||||||
|
tilt: 4,
|
||||||
|
);
|
||||||
|
_expectTransformedEvent(
|
||||||
|
original: up,
|
||||||
|
transform: transform,
|
||||||
|
localPosition: localPosition,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _expectTransformedEvent({
|
||||||
|
@required PointerEvent original,
|
||||||
|
@required Matrix4 transform,
|
||||||
|
Offset localDelta,
|
||||||
|
Offset localPosition,
|
||||||
|
}) {
|
||||||
|
expect(original.position, original.localPosition);
|
||||||
|
expect(original.delta, original.localDelta);
|
||||||
|
expect(original.original, isNull);
|
||||||
|
expect(original.transform, isNull);
|
||||||
|
|
||||||
|
final PointerEvent transformed = original.transformed(transform);
|
||||||
|
expect(transformed.original, same(original));
|
||||||
|
expect(transformed.transform, transform);
|
||||||
|
expect(transformed.localDelta, localDelta ?? original.localDelta);
|
||||||
|
expect(transformed.localPosition, localPosition ?? original.localPosition);
|
||||||
|
|
||||||
|
expect(transformed.buttons, original.buttons);
|
||||||
|
expect(transformed.delta, original.delta);
|
||||||
|
expect(transformed.device, original.device);
|
||||||
|
expect(transformed.distance, original.distance);
|
||||||
|
expect(transformed.distanceMax, original.distanceMax);
|
||||||
|
expect(transformed.distanceMin, original.distanceMin);
|
||||||
|
expect(transformed.down, original.down);
|
||||||
|
expect(transformed.kind, original.kind);
|
||||||
|
expect(transformed.obscured, original.obscured);
|
||||||
|
expect(transformed.orientation, original.orientation);
|
||||||
|
expect(transformed.platformData, original.platformData);
|
||||||
|
expect(transformed.pointer, original.pointer);
|
||||||
|
expect(transformed.position, original.position);
|
||||||
|
expect(transformed.pressure, original.pressure);
|
||||||
|
expect(transformed.pressureMax, original.pressureMax);
|
||||||
|
expect(transformed.pressureMin, original.pressureMin);
|
||||||
|
expect(transformed.radiusMajor, original.radiusMajor);
|
||||||
|
expect(transformed.radiusMax, original.radiusMax);
|
||||||
|
expect(transformed.radiusMin, original.radiusMin);
|
||||||
|
expect(transformed.radiusMinor, original.radiusMinor);
|
||||||
|
expect(transformed.size, original.size);
|
||||||
|
expect(transformed.synthesized, original.synthesized);
|
||||||
|
expect(transformed.tilt, original.tilt);
|
||||||
|
expect(transformed.timeStamp, original.timeStamp);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import '../flutter_test_alternative.dart';
|
import '../flutter_test_alternative.dart';
|
||||||
|
|
||||||
@ -11,10 +12,13 @@ void main() {
|
|||||||
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
|
||||||
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
|
||||||
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
|
||||||
|
final Matrix4 transform = Matrix4.translationValues(40.0, 150.0, 0.0);
|
||||||
|
|
||||||
final HitTestResult wrapped = HitTestResult();
|
final HitTestResult wrapped = MyHitTestResult()
|
||||||
|
..publicPushTransform(transform);
|
||||||
wrapped.add(entry1);
|
wrapped.add(entry1);
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
|
||||||
|
expect(entry1.transform, transform);
|
||||||
|
|
||||||
final HitTestResult wrapping = HitTestResult.wrap(wrapped);
|
final HitTestResult wrapping = HitTestResult.wrap(wrapped);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
|
||||||
@ -23,10 +27,12 @@ void main() {
|
|||||||
wrapping.add(entry2);
|
wrapping.add(entry2);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
|
||||||
|
expect(entry2.transform, transform);
|
||||||
|
|
||||||
wrapped.add(entry3);
|
wrapped.add(entry3);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
|
expect(entry3.transform, transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,3 +42,7 @@ class _DummyHitTestTarget implements HitTestTarget {
|
|||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyHitTestResult extends HitTestResult {
|
||||||
|
void publicPushTransform(Matrix4 transform) => pushTransform(transform);
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Should route pointers', () {
|
test('Should route pointers', () {
|
||||||
@ -149,4 +150,53 @@ void main() {
|
|||||||
|
|
||||||
FlutterError.onError = previousErrorHandler;
|
FlutterError.onError = previousErrorHandler;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should transform events', () {
|
||||||
|
final List<PointerEvent> events = <PointerEvent>[];
|
||||||
|
final List<PointerEvent> globalEvents = <PointerEvent>[];
|
||||||
|
final PointerRouter router = PointerRouter();
|
||||||
|
final Matrix4 transform = (Matrix4.identity()..scale(1 / 2.0, 1 / 2.0, 1.0)) * Matrix4.translationValues(-10, -30, 0);
|
||||||
|
|
||||||
|
router.addRoute(1, (PointerEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
}, transform);
|
||||||
|
|
||||||
|
router.addGlobalRoute((PointerEvent event) {
|
||||||
|
globalEvents.add(event);
|
||||||
|
}, transform);
|
||||||
|
|
||||||
|
final TestPointer pointer1 = TestPointer(1);
|
||||||
|
const Offset firstPosition = Offset(16, 36);
|
||||||
|
router.route(pointer1.down(firstPosition));
|
||||||
|
|
||||||
|
expect(events.single.transform, transform);
|
||||||
|
expect(events.single.position, firstPosition);
|
||||||
|
expect(events.single.delta, Offset.zero);
|
||||||
|
expect(events.single.localPosition, const Offset(3, 3));
|
||||||
|
expect(events.single.localDelta, Offset.zero);
|
||||||
|
|
||||||
|
expect(globalEvents.single.transform, transform);
|
||||||
|
expect(globalEvents.single.position, firstPosition);
|
||||||
|
expect(globalEvents.single.delta, Offset.zero);
|
||||||
|
expect(globalEvents.single.localPosition, const Offset(3, 3));
|
||||||
|
expect(globalEvents.single.localDelta, Offset.zero);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
globalEvents.clear();
|
||||||
|
|
||||||
|
const Offset newPosition = Offset(20, 40);
|
||||||
|
router.route(pointer1.move(newPosition));
|
||||||
|
|
||||||
|
expect(events.single.transform, transform);
|
||||||
|
expect(events.single.position, newPosition);
|
||||||
|
expect(events.single.delta, newPosition - firstPosition);
|
||||||
|
expect(events.single.localPosition, const Offset(5, 5));
|
||||||
|
expect(events.single.localDelta, const Offset(2, 2));
|
||||||
|
|
||||||
|
expect(globalEvents.single.transform, transform);
|
||||||
|
expect(globalEvents.single.position, newPosition);
|
||||||
|
expect(globalEvents.single.delta, newPosition - firstPosition);
|
||||||
|
expect(globalEvents.single.localPosition, const Offset(5, 5));
|
||||||
|
expect(globalEvents.single.localDelta, const Offset(2, 2));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import '../flutter_test_alternative.dart';
|
import '../flutter_test_alternative.dart';
|
||||||
|
|
||||||
@ -67,4 +68,22 @@ void main() {
|
|||||||
expect(first.callbackRan, isTrue);
|
expect(first.callbackRan, isTrue);
|
||||||
expect(second.callbackRan, isFalse);
|
expect(second.callbackRan, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('works with transformed events', () {
|
||||||
|
final PointerSignalResolver resolver = PointerSignalResolver();
|
||||||
|
const PointerSignalEvent originalEvent = PointerScrollEvent();
|
||||||
|
final PointerSignalEvent transformedEvent = originalEvent
|
||||||
|
.transformed(Matrix4.translationValues(10.0, 20.0, 0.0));
|
||||||
|
|
||||||
|
expect(originalEvent, isNot(same(transformedEvent)));
|
||||||
|
expect(transformedEvent.original, same(originalEvent));
|
||||||
|
|
||||||
|
final List<PointerSignalEvent> events = <PointerSignalEvent>[];
|
||||||
|
resolver.register(transformedEvent, (PointerSignalEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
});
|
||||||
|
resolver.resolve(originalEvent);
|
||||||
|
|
||||||
|
expect(events.single, same(transformedEvent));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -26,4 +26,28 @@ void main() {
|
|||||||
final TestGestureRecognizer recognizer = TestGestureRecognizer(debugOwner: 0);
|
final TestGestureRecognizer recognizer = TestGestureRecognizer(debugOwner: 0);
|
||||||
expect(recognizer, hasAGoodToStringDeep);
|
expect(recognizer, hasAGoodToStringDeep);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('OffsetPair', () {
|
||||||
|
const OffsetPair offset1 = OffsetPair(
|
||||||
|
local: Offset(10, 20),
|
||||||
|
global: Offset(30, 40),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(offset1.local, const Offset(10, 20));
|
||||||
|
expect(offset1.global, const Offset(30, 40));
|
||||||
|
|
||||||
|
const OffsetPair offset2 = OffsetPair(
|
||||||
|
local: Offset(50, 60),
|
||||||
|
global: Offset(70, 80),
|
||||||
|
);
|
||||||
|
|
||||||
|
final OffsetPair sum = offset2 + offset1;
|
||||||
|
expect(sum.local, const Offset(60, 80));
|
||||||
|
expect(sum.global, const Offset(100, 120));
|
||||||
|
|
||||||
|
final OffsetPair difference = offset2 - offset1;
|
||||||
|
expect(difference.local, const Offset(40, 40));
|
||||||
|
expect(difference.global, const Offset(40, 40));
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
98
packages/flutter/test/gestures/transformed_double_tap.dart
Normal file
98
packages/flutter/test/gestures/transformed_double_tap.dart
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2019 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 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
|
||||||
|
int doubleTapCount = 0;
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 2.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onDoubleTap: () {
|
||||||
|
doubleTapCount++;
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just below kTouchSlop should recognize tap.
|
||||||
|
final Offset center = tester.getCenter(find.byKey(redContainer));
|
||||||
|
TestGesture gesture = await tester.startGesture(center);
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump(kDoubleTapMinTime);
|
||||||
|
gesture = await tester.startGesture(center + const Offset(kDoubleTapSlop - 1, 0));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(doubleTapCount, 1);
|
||||||
|
|
||||||
|
doubleTapCount = 0;
|
||||||
|
|
||||||
|
gesture = await tester.startGesture(center);
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump(kDoubleTapMinTime);
|
||||||
|
gesture = await tester.startGesture(center + const Offset(kDoubleTapSlop + 1, 0));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(doubleTapCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
|
||||||
|
int doubleTapCount = 0;
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: GestureDetector(
|
||||||
|
onDoubleTap: () {
|
||||||
|
doubleTapCount++;
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just below kTouchSlop should recognize tap.
|
||||||
|
final Offset center = tester.getCenter(find.byKey(redContainer));
|
||||||
|
TestGesture gesture = await tester.startGesture(center);
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump(kDoubleTapMinTime);
|
||||||
|
gesture = await tester.startGesture(center + const Offset(kDoubleTapSlop - 1, 0));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(doubleTapCount, 1);
|
||||||
|
|
||||||
|
doubleTapCount = 0;
|
||||||
|
|
||||||
|
gesture = await tester.startGesture(center);
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump(kDoubleTapMinTime);
|
||||||
|
gesture = await tester.startGesture(center + const Offset(kDoubleTapSlop + 1, 0));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(doubleTapCount, 0);
|
||||||
|
});
|
||||||
|
}
|
206
packages/flutter/test/gestures/transformed_long_press_test.dart
Normal file
206
packages/flutter/test/gestures/transformed_long_press_test.dart
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// Copyright 2019 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 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('gets local corrdinates', (WidgetTester tester) async {
|
||||||
|
int longPressCount = 0;
|
||||||
|
int longPressUpCount = 0;
|
||||||
|
final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[];
|
||||||
|
final List<LongPressMoveUpdateDetails> moveDetails = <LongPressMoveUpdateDetails>[];
|
||||||
|
final List<LongPressStartDetails> startDetails = <LongPressStartDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
longPressCount++;
|
||||||
|
},
|
||||||
|
onLongPressEnd: (LongPressEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
|
||||||
|
moveDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressStart: (LongPressStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressUp: () {
|
||||||
|
longPressUpCount++;
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.longPressAt(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
expect(longPressCount, 1);
|
||||||
|
expect(longPressUpCount, 1);
|
||||||
|
expect(moveDetails, isEmpty);
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(endDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('scaled up', (WidgetTester tester) async {
|
||||||
|
int longPressCount = 0;
|
||||||
|
int longPressUpCount = 0;
|
||||||
|
final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[];
|
||||||
|
final List<LongPressMoveUpdateDetails> moveDetails = <LongPressMoveUpdateDetails>[];
|
||||||
|
final List<LongPressStartDetails> startDetails = <LongPressStartDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 2.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
longPressCount++;
|
||||||
|
},
|
||||||
|
onLongPressEnd: (LongPressEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
|
||||||
|
moveDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressStart: (LongPressStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressUp: () {
|
||||||
|
longPressUpCount++;
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(0, 10.0));
|
||||||
|
await tester.pump(kLongPressTimeout);
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(longPressCount, 1);
|
||||||
|
expect(longPressUpCount, 1);
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails.single.localPosition, const Offset(50, 75 + 10.0 / 2.0));
|
||||||
|
expect(endDetails.single.globalPosition, const Offset(400, 300.0 + 10.0));
|
||||||
|
expect(moveDetails, isEmpty); // moved before long press was detected.
|
||||||
|
|
||||||
|
startDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
longPressCount = 0;
|
||||||
|
longPressUpCount = 0;
|
||||||
|
|
||||||
|
// Move after recognized.
|
||||||
|
gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await tester.pump(kLongPressTimeout);
|
||||||
|
await gesture.moveBy(const Offset(0, 100));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(longPressCount, 1);
|
||||||
|
expect(longPressUpCount, 1);
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails.single.localPosition, const Offset(50, 75 + 100.0 / 2.0));
|
||||||
|
expect(endDetails.single.globalPosition, const Offset(400, 300.0 + 100.0));
|
||||||
|
expect(moveDetails.single.localPosition, const Offset(50, 75 + 100.0 / 2.0));
|
||||||
|
expect(moveDetails.single.globalPosition, const Offset(400, 300.0 + 100.0));
|
||||||
|
expect(moveDetails.single.offsetFromOrigin, const Offset(0, 100.0));
|
||||||
|
expect(moveDetails.single.localOffsetFromOrigin, const Offset(0, 100.0 / 2.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('scaled down', (WidgetTester tester) async {
|
||||||
|
int longPressCount = 0;
|
||||||
|
int longPressUpCount = 0;
|
||||||
|
final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[];
|
||||||
|
final List<LongPressMoveUpdateDetails> moveDetails = <LongPressMoveUpdateDetails>[];
|
||||||
|
final List<LongPressStartDetails> startDetails = <LongPressStartDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
longPressCount++;
|
||||||
|
},
|
||||||
|
onLongPressEnd: (LongPressEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
|
||||||
|
moveDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressStart: (LongPressStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onLongPressUp: () {
|
||||||
|
longPressUpCount++;
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(0, 10.0));
|
||||||
|
await tester.pump(kLongPressTimeout);
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(longPressCount, 1);
|
||||||
|
expect(longPressUpCount, 1);
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails.single.localPosition, const Offset(50, 75 + 10.0 * 2.0));
|
||||||
|
expect(endDetails.single.globalPosition, const Offset(400, 300.0 + 10.0));
|
||||||
|
expect(moveDetails, isEmpty); // moved before long press was detected.
|
||||||
|
|
||||||
|
startDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
longPressCount = 0;
|
||||||
|
longPressUpCount = 0;
|
||||||
|
|
||||||
|
// Move after recognized.
|
||||||
|
gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await tester.pump(kLongPressTimeout);
|
||||||
|
await gesture.moveBy(const Offset(0, 100));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(longPressCount, 1);
|
||||||
|
expect(longPressUpCount, 1);
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails.single.localPosition, const Offset(50, 75 + 100.0 * 2.0));
|
||||||
|
expect(endDetails.single.globalPosition, const Offset(400, 300.0 + 100.0));
|
||||||
|
expect(moveDetails.single.localPosition, const Offset(50, 75 + 100.0 * 2.0));
|
||||||
|
expect(moveDetails.single.globalPosition, const Offset(400, 300.0 + 100.0));
|
||||||
|
expect(moveDetails.single.offsetFromOrigin, const Offset(0, 100.0));
|
||||||
|
expect(moveDetails.single.localOffsetFromOrigin, const Offset(0, 100.0 * 2.0));
|
||||||
|
});
|
||||||
|
}
|
668
packages/flutter/test/gestures/transformed_monodrag_test.dart
Normal file
668
packages/flutter/test/gestures/transformed_monodrag_test.dart
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
// Copyright 2019 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:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Horizontal', () {
|
||||||
|
testWidgets('gets local corrdinates', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onHorizontalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onHorizontalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(100, 0));
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(updateDetails.last.localPosition, const Offset(50 + 100.0, 75));
|
||||||
|
expect(updateDetails.last.globalPosition, const Offset(400 + 100.0, 300));
|
||||||
|
expect(
|
||||||
|
updateDetails.fold(Offset.zero, (Offset offset, DragUpdateDetails details) => offset + details.delta),
|
||||||
|
const Offset(100, 0),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
updateDetails.fold(0.0, (double offset, DragUpdateDetails details) => offset + details.primaryDelta),
|
||||||
|
100.0,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 2.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onHorizontalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onHorizontalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// Competing gesture detector.
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just above kTouchSlop should recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop + 1, 0));
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) / 2, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300));
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move just below kTouchSlop does not recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop - 1, 0));
|
||||||
|
expect(dragCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, isEmpty);
|
||||||
|
expect(startDetails, isEmpty);
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move in two separate movements
|
||||||
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(kTouchSlop + 1, 30));
|
||||||
|
await gesture.moveBy(const Offset(100, 10));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) / 2, 75.0 + 30.0 / 2));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300 + 30.0));
|
||||||
|
expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(100.0 / 2, 10 / 2));
|
||||||
|
expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(100.0, 10.0));
|
||||||
|
expect(updateDetails.single.delta, const Offset(100.0 / 2, 0.0));
|
||||||
|
expect(updateDetails.single.primaryDelta, 100.0 / 2);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: GestureDetector(
|
||||||
|
onHorizontalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onHorizontalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// Competing gesture detector.
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just above kTouchSlop should recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop + 1, 0));
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) * 2, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300));
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move just below kTouchSlop does not recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop - 1, 0));
|
||||||
|
expect(dragCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, isEmpty);
|
||||||
|
expect(startDetails, isEmpty);
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move in two separate movements
|
||||||
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(kTouchSlop + 1, 30));
|
||||||
|
await gesture.moveBy(const Offset(100, 10));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) * 2, 75.0 + 30.0 * 2));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300 + 30.0));
|
||||||
|
expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(100.0 * 2, 10.0 * 2.0));
|
||||||
|
expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(100.0, 10.0));
|
||||||
|
expect(updateDetails.single.delta, const Offset(100.0 * 2.0, 0.0));
|
||||||
|
expect(updateDetails.single.primaryDelta, 100.0 * 2);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when roateted 45 degrees', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: math.pi / 4,
|
||||||
|
child: GestureDetector(
|
||||||
|
onHorizontalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onHorizontalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// Competing gesture detector.
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just below kTouchSlop should not recognize drag.
|
||||||
|
const Offset moveBy1 = Offset(kTouchSlop/ 2, kTouchSlop / 2);
|
||||||
|
expect(moveBy1.distance, lessThan(kTouchSlop));
|
||||||
|
await tester.drag(find.byKey(redContainer), moveBy1);
|
||||||
|
expect(dragCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, within(distance: 0.0001, from: const Offset(50, 75)));
|
||||||
|
expect(downDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400, 300)));
|
||||||
|
expect(endDetails, isEmpty);
|
||||||
|
expect(startDetails, isEmpty);
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move above kTouchSlop recognizes drag.
|
||||||
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(kTouchSlop, kTouchSlop));
|
||||||
|
await gesture.moveBy(const Offset(3, 4));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, within(distance: 0.0001, from: const Offset(50, 75)));
|
||||||
|
expect(downDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400, 300)));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails, hasLength(1));
|
||||||
|
expect(updateDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400 + kTouchSlop + 3, 300 + kTouchSlop + 4)));
|
||||||
|
expect(updateDetails.single.delta, within(distance: 0.1, from: const Offset(5, 0.0))); // sqrt(3^2 + 4^2)
|
||||||
|
expect(updateDetails.single.primaryDelta, within(distance: 0.1, from: 5.0)); // sqrt(3^2 + 4^2)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Vertical', () {
|
||||||
|
testWidgets('gets local corrdinates', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onVerticalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onVerticalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(0, 100));
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(updateDetails.last.localPosition, const Offset(50, 75 + 100.0));
|
||||||
|
expect(updateDetails.last.globalPosition, const Offset(400, 300 + 100.0));
|
||||||
|
expect(
|
||||||
|
updateDetails.fold(Offset.zero, (Offset offset, DragUpdateDetails details) => offset + details.delta),
|
||||||
|
const Offset(0, 100),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
updateDetails.fold(0.0, (double offset, DragUpdateDetails details) => offset + details.primaryDelta),
|
||||||
|
100.0,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 2.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onVerticalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onVerticalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// Competing gesture detector.
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just above kTouchSlop should recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop + 1));
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop + 1) / 2));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop + 1)));
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move just below kTouchSlop does not recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop - 1));
|
||||||
|
expect(dragCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, isEmpty);
|
||||||
|
expect(startDetails, isEmpty);
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move in two separate movements
|
||||||
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(30, kTouchSlop + 1));
|
||||||
|
await gesture.moveBy(const Offset(10, 100));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50 + 30.0 / 2, 75.0 + (kTouchSlop + 1) / 2));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400 + 30.0, 300 + (kTouchSlop + 1)));
|
||||||
|
expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(10.0 / 2, 100.0 / 2));
|
||||||
|
expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(10.0, 100.0));
|
||||||
|
expect(updateDetails.single.delta, const Offset(0.0, 100.0 / 2));
|
||||||
|
expect(updateDetails.single.primaryDelta, 100.0 / 2);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: GestureDetector(
|
||||||
|
onVerticalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onVerticalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// Competing gesture detector.
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just above kTouchSlop should recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop + 1));
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop + 1) * 2));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop + 1)));
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move just below kTouchSlop does not recognize drag.
|
||||||
|
await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop - 1));
|
||||||
|
expect(dragCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, isEmpty);
|
||||||
|
expect(startDetails, isEmpty);
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move in two separate movements
|
||||||
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(30, kTouchSlop + 1));
|
||||||
|
await gesture.moveBy(const Offset(10, 100));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails.single.localPosition, const Offset(50 + 30.0 * 2, 75.0 + (kTouchSlop + 1) * 2));
|
||||||
|
expect(startDetails.single.globalPosition, const Offset(400 + 30.0, 300 + (kTouchSlop + 1)));
|
||||||
|
expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(10.0 * 2, 100.0 * 2.0));
|
||||||
|
expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(10.0, 100.0));
|
||||||
|
expect(updateDetails.single.delta, const Offset(0.0, 100.0 * 2.0));
|
||||||
|
expect(updateDetails.single.primaryDelta, 100.0 * 2);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when roateted 45 degrees', (WidgetTester tester) async {
|
||||||
|
int dragCancelCount = 0;
|
||||||
|
final List<DragDownDetails> downDetails = <DragDownDetails>[];
|
||||||
|
final List<DragEndDetails> endDetails = <DragEndDetails>[];
|
||||||
|
final List<DragStartDetails> startDetails = <DragStartDetails>[];
|
||||||
|
final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: math.pi / 4,
|
||||||
|
child: GestureDetector(
|
||||||
|
onVerticalDragCancel: () {
|
||||||
|
dragCancelCount++;
|
||||||
|
},
|
||||||
|
onVerticalDragDown: (DragDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragEnd: (DragEndDetails details) {
|
||||||
|
endDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragStart: (DragStartDetails details) {
|
||||||
|
startDetails.add(details);
|
||||||
|
},
|
||||||
|
onVerticalDragUpdate: (DragUpdateDetails details) {
|
||||||
|
updateDetails.add(details);
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
// Competing gesture detector.
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just below kTouchSlop should not recognize drag.
|
||||||
|
const Offset moveBy1 = Offset(kTouchSlop/ 2, kTouchSlop / 2);
|
||||||
|
expect(moveBy1.distance, lessThan(kTouchSlop));
|
||||||
|
await tester.drag(find.byKey(redContainer), moveBy1);
|
||||||
|
expect(dragCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, within(distance: 0.0001, from: const Offset(50, 75)));
|
||||||
|
expect(downDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400, 300)));
|
||||||
|
expect(endDetails, isEmpty);
|
||||||
|
expect(startDetails, isEmpty);
|
||||||
|
expect(updateDetails, isEmpty);
|
||||||
|
|
||||||
|
dragCancelCount = 0;
|
||||||
|
downDetails.clear();
|
||||||
|
endDetails.clear();
|
||||||
|
startDetails.clear();
|
||||||
|
updateDetails.clear();
|
||||||
|
|
||||||
|
// Move above kTouchSlop recognizes drag.
|
||||||
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(kTouchSlop, kTouchSlop));
|
||||||
|
await gesture.moveBy(const Offset(-4, 3));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(dragCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, within(distance: 0.0001, from: const Offset(50, 75)));
|
||||||
|
expect(downDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400, 300)));
|
||||||
|
expect(endDetails, hasLength(1));
|
||||||
|
expect(startDetails, hasLength(1));
|
||||||
|
expect(updateDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400 + kTouchSlop - 4, 300 + kTouchSlop + 3)));
|
||||||
|
expect(updateDetails.single.delta, within(distance: 0.1, from: const Offset(0.0, 5.0))); // sqrt(3^2 + 4^2)
|
||||||
|
expect(updateDetails.single.primaryDelta, within(distance: 0.1, from: 5.0)); // sqrt(3^2 + 4^2)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
177
packages/flutter/test/gestures/transformed_tap_test.dart
Normal file
177
packages/flutter/test/gestures/transformed_tap_test.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// Copyright 2019 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 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('gets local corrdinates', (WidgetTester tester) async {
|
||||||
|
int tapCount = 0;
|
||||||
|
int tapCancelCount = 0;
|
||||||
|
final List<TapDownDetails> downDetails = <TapDownDetails>[];
|
||||||
|
final List<TapUpDetails> upDetails = <TapUpDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
tapCount++;
|
||||||
|
},
|
||||||
|
onTapCancel: () {
|
||||||
|
tapCancelCount++;
|
||||||
|
},
|
||||||
|
onTapDown: (TapDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onTapUp: (TapUpDetails details) {
|
||||||
|
upDetails.add(details);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tapAt(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
expect(tapCount, 1);
|
||||||
|
expect(tapCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(upDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(upDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
|
||||||
|
int tapCount = 0;
|
||||||
|
int tapCancelCount = 0;
|
||||||
|
final List<TapDownDetails> downDetails = <TapDownDetails>[];
|
||||||
|
final List<TapUpDetails> upDetails = <TapUpDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 2.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
tapCount++;
|
||||||
|
},
|
||||||
|
onTapCancel: () {
|
||||||
|
tapCancelCount++;
|
||||||
|
},
|
||||||
|
onTapDown: (TapDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onTapUp: (TapUpDetails details) {
|
||||||
|
upDetails.add(details);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just below kTouchSlop should recognize tap.
|
||||||
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(0, kTouchSlop - 1));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(tapCount, 1);
|
||||||
|
expect(tapCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(upDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop - 1) / 2.0));
|
||||||
|
expect(upDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop - 1)));
|
||||||
|
|
||||||
|
downDetails.clear();
|
||||||
|
upDetails.clear();
|
||||||
|
tapCount = 0;
|
||||||
|
tapCancelCount = 0;
|
||||||
|
|
||||||
|
// Move more then kTouchSlop should cancel.
|
||||||
|
gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(0, kTouchSlop + 1));
|
||||||
|
await gesture.up();
|
||||||
|
expect(tapCount, 0);
|
||||||
|
expect(tapCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(upDetails, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
|
||||||
|
int tapCount = 0;
|
||||||
|
int tapCancelCount = 0;
|
||||||
|
final List<TapDownDetails> downDetails = <TapDownDetails>[];
|
||||||
|
final List<TapUpDetails> upDetails = <TapUpDetails>[];
|
||||||
|
|
||||||
|
final Key redContainer = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
tapCount++;
|
||||||
|
},
|
||||||
|
onTapCancel: () {
|
||||||
|
tapCancelCount++;
|
||||||
|
},
|
||||||
|
onTapDown: (TapDownDetails details) {
|
||||||
|
downDetails.add(details);
|
||||||
|
},
|
||||||
|
onTapUp: (TapUpDetails details) {
|
||||||
|
upDetails.add(details);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: redContainer,
|
||||||
|
width: 100,
|
||||||
|
height: 150,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move just below kTouchSlop should recognize tap.
|
||||||
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(0, kTouchSlop - 1));
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(tapCount, 1);
|
||||||
|
expect(tapCancelCount, 0);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(upDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop - 1) * 2.0));
|
||||||
|
expect(upDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop - 1)));
|
||||||
|
|
||||||
|
downDetails.clear();
|
||||||
|
upDetails.clear();
|
||||||
|
tapCount = 0;
|
||||||
|
tapCancelCount = 0;
|
||||||
|
|
||||||
|
// Move more then kTouchSlop should cancel.
|
||||||
|
gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
|
||||||
|
await gesture.moveBy(const Offset(0, kTouchSlop + 1));
|
||||||
|
await gesture.up();
|
||||||
|
expect(tapCount, 0);
|
||||||
|
expect(tapCancelCount, 1);
|
||||||
|
expect(downDetails.single.localPosition, const Offset(50, 75));
|
||||||
|
expect(downDetails.single.globalPosition, const Offset(400, 300));
|
||||||
|
expect(upDetails, isEmpty);
|
||||||
|
});
|
||||||
|
}
|
@ -265,10 +265,13 @@ void main() {
|
|||||||
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
|
||||||
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
|
||||||
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
|
||||||
|
final Matrix4 transform = Matrix4.translationValues(40.0, 150.0, 0.0);
|
||||||
|
|
||||||
final HitTestResult wrapped = HitTestResult();
|
final HitTestResult wrapped = MyHitTestResult()
|
||||||
|
..publicPushTransform(transform);
|
||||||
wrapped.add(entry1);
|
wrapped.add(entry1);
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
|
||||||
|
expect(entry1.transform, transform);
|
||||||
|
|
||||||
final BoxHitTestResult wrapping = BoxHitTestResult.wrap(wrapped);
|
final BoxHitTestResult wrapping = BoxHitTestResult.wrap(wrapped);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
|
||||||
@ -277,10 +280,12 @@ void main() {
|
|||||||
wrapping.add(entry2);
|
wrapping.add(entry2);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
|
||||||
|
expect(entry2.transform, transform);
|
||||||
|
|
||||||
wrapped.add(entry3);
|
wrapped.add(entry3);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
|
expect(entry3.transform, transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addWithPaintTransform', () {
|
test('addWithPaintTransform', () {
|
||||||
@ -517,3 +522,7 @@ class _DummyHitTestTarget implements HitTestTarget {
|
|||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyHitTestResult extends HitTestResult {
|
||||||
|
void publicPushTransform(Matrix4 transform) => pushTransform(transform);
|
||||||
|
}
|
||||||
|
@ -834,10 +834,13 @@ void main() {
|
|||||||
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
|
||||||
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
|
||||||
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
|
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
|
||||||
|
final Matrix4 transform = Matrix4.translationValues(40.0, 150.0, 0.0);
|
||||||
|
|
||||||
final HitTestResult wrapped = HitTestResult();
|
final HitTestResult wrapped = MyHitTestResult()
|
||||||
|
..publicPushTransform(transform);
|
||||||
wrapped.add(entry1);
|
wrapped.add(entry1);
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
|
||||||
|
expect(entry1.transform, transform);
|
||||||
|
|
||||||
final SliverHitTestResult wrapping = SliverHitTestResult.wrap(wrapped);
|
final SliverHitTestResult wrapping = SliverHitTestResult.wrap(wrapped);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
|
||||||
@ -846,10 +849,12 @@ void main() {
|
|||||||
wrapping.add(entry2);
|
wrapping.add(entry2);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
|
||||||
|
expect(entry2.transform, transform);
|
||||||
|
|
||||||
wrapped.add(entry3);
|
wrapped.add(entry3);
|
||||||
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
|
expect(entry3.transform, transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addWithAxisOffset', () {
|
test('addWithAxisOffset', () {
|
||||||
@ -924,3 +929,7 @@ class _DummyHitTestTarget implements HitTestTarget {
|
|||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyHitTestResult extends HitTestResult {
|
||||||
|
void publicPushTransform(Matrix4 transform) => pushTransform(transform);
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -594,6 +596,7 @@ void main() {
|
|||||||
|
|
||||||
await gesture.removePointer();
|
await gesture.removePointer();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Exit event when unplugging mouse should have a position', (WidgetTester tester) async {
|
testWidgets('Exit event when unplugging mouse should have a position', (WidgetTester tester) async {
|
||||||
final List<PointerEnterEvent> enter = <PointerEnterEvent>[];
|
final List<PointerEnterEvent> enter = <PointerEnterEvent>[];
|
||||||
final List<PointerHoverEvent> hover = <PointerHoverEvent>[];
|
final List<PointerHoverEvent> hover = <PointerHoverEvent>[];
|
||||||
@ -640,4 +643,323 @@ void main() {
|
|||||||
expect(exit.single.delta, const Offset(0.0, 0.0));
|
expect(exit.single.delta, const Offset(0.0, 0.0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('transformed events', () {
|
||||||
|
testWidgets('simple offset for touch/signal', (WidgetTester tester) async {
|
||||||
|
final List<PointerEvent> events = <PointerEvent>[];
|
||||||
|
final Key key = UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (PointerDownEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerUp: (PointerUpEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerMove: (PointerMoveEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerSignal: (PointerSignalEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: key,
|
||||||
|
color: Colors.red,
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const Offset moved = Offset(20, 30);
|
||||||
|
final Offset center = tester.getCenter(find.byKey(key));
|
||||||
|
final Offset topLeft = tester.getTopLeft(find.byKey(key));
|
||||||
|
final TestGesture gesture = await tester.startGesture(center);
|
||||||
|
await gesture.moveBy(moved);
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(events, hasLength(3));
|
||||||
|
final PointerDownEvent down = events[0];
|
||||||
|
final PointerMoveEvent move = events[1];
|
||||||
|
final PointerUpEvent up = events[2];
|
||||||
|
|
||||||
|
final Matrix4 expectedTransform = Matrix4.translationValues(-topLeft.dx, -topLeft.dy, 0);
|
||||||
|
|
||||||
|
expect(center, isNot(const Offset(50, 50)));
|
||||||
|
|
||||||
|
expect(down.localPosition, const Offset(50, 50));
|
||||||
|
expect(down.position, center);
|
||||||
|
expect(down.delta, Offset.zero);
|
||||||
|
expect(down.localDelta, Offset.zero);
|
||||||
|
expect(down.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(move.localPosition, const Offset(50, 50) + moved);
|
||||||
|
expect(move.position, center + moved);
|
||||||
|
expect(move.delta, moved);
|
||||||
|
expect(move.localDelta, moved);
|
||||||
|
expect(move.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(up.localPosition, const Offset(50, 50) + moved);
|
||||||
|
expect(up.position, center + moved);
|
||||||
|
expect(up.delta, Offset.zero);
|
||||||
|
expect(up.localDelta, Offset.zero);
|
||||||
|
expect(up.transform, expectedTransform);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
await scrollAt(center, tester);
|
||||||
|
expect(events.single.localPosition, const Offset(50, 50));
|
||||||
|
expect(events.single.position, center);
|
||||||
|
expect(events.single.delta, Offset.zero);
|
||||||
|
expect(events.single.localDelta, Offset.zero);
|
||||||
|
expect(events.single.transform, expectedTransform);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('scaled for touch/signal', (WidgetTester tester) async {
|
||||||
|
final List<PointerEvent> events = <PointerEvent>[];
|
||||||
|
final Key key = UniqueKey();
|
||||||
|
|
||||||
|
const double scaleFactor = 2;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Transform(
|
||||||
|
transform: Matrix4.identity()..scale(scaleFactor),
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (PointerDownEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerUp: (PointerUpEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerMove: (PointerMoveEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerSignal: (PointerSignalEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: key,
|
||||||
|
color: Colors.red,
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const Offset moved = Offset(20, 30);
|
||||||
|
final Offset center = tester.getCenter(find.byKey(key));
|
||||||
|
final TestGesture gesture = await tester.startGesture(center);
|
||||||
|
await gesture.moveBy(moved);
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(events, hasLength(3));
|
||||||
|
final PointerDownEvent down = events[0];
|
||||||
|
final PointerMoveEvent move = events[1];
|
||||||
|
final PointerUpEvent up = events[2];
|
||||||
|
|
||||||
|
final Matrix4 expectedTransform = Matrix4.identity()
|
||||||
|
..scale(1 / scaleFactor, 1 / scaleFactor, 1.0);
|
||||||
|
|
||||||
|
expect(center, isNot(const Offset(50, 50)));
|
||||||
|
|
||||||
|
expect(down.localPosition, const Offset(50, 50));
|
||||||
|
expect(down.position, center);
|
||||||
|
expect(down.delta, Offset.zero);
|
||||||
|
expect(down.localDelta, Offset.zero);
|
||||||
|
expect(down.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(move.localPosition, const Offset(50, 50) + moved / scaleFactor);
|
||||||
|
expect(move.position, center + moved);
|
||||||
|
expect(move.delta, moved);
|
||||||
|
expect(move.localDelta, moved / scaleFactor);
|
||||||
|
expect(move.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(up.localPosition, const Offset(50, 50) + moved / scaleFactor);
|
||||||
|
expect(up.position, center + moved);
|
||||||
|
expect(up.delta, Offset.zero);
|
||||||
|
expect(up.localDelta, Offset.zero);
|
||||||
|
expect(up.transform, expectedTransform);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
await scrollAt(center, tester);
|
||||||
|
expect(events.single.localPosition, const Offset(50, 50));
|
||||||
|
expect(events.single.position, center);
|
||||||
|
expect(events.single.delta, Offset.zero);
|
||||||
|
expect(events.single.localDelta, Offset.zero);
|
||||||
|
expect(events.single.transform, expectedTransform);
|
||||||
|
|
||||||
|
await gesture.removePointer();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('scaled and offset for touch/signal', (WidgetTester tester) async {
|
||||||
|
final List<PointerEvent> events = <PointerEvent>[];
|
||||||
|
final Key key = UniqueKey();
|
||||||
|
|
||||||
|
const double scaleFactor = 2;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform(
|
||||||
|
transform: Matrix4.identity()..scale(scaleFactor),
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (PointerDownEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerUp: (PointerUpEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerMove: (PointerMoveEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerSignal: (PointerSignalEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: key,
|
||||||
|
color: Colors.red,
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const Offset moved = Offset(20, 30);
|
||||||
|
final Offset center = tester.getCenter(find.byKey(key));
|
||||||
|
final Offset topLeft = tester.getTopLeft(find.byKey(key));
|
||||||
|
final TestGesture gesture = await tester.startGesture(center);
|
||||||
|
await gesture.moveBy(moved);
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(events, hasLength(3));
|
||||||
|
final PointerDownEvent down = events[0];
|
||||||
|
final PointerMoveEvent move = events[1];
|
||||||
|
final PointerUpEvent up = events[2];
|
||||||
|
|
||||||
|
final Matrix4 expectedTransform = Matrix4.identity()
|
||||||
|
..scale(1 / scaleFactor, 1 / scaleFactor, 1.0)
|
||||||
|
..translate(-topLeft.dx, -topLeft.dy, 0);
|
||||||
|
|
||||||
|
expect(center, isNot(const Offset(50, 50)));
|
||||||
|
|
||||||
|
expect(down.localPosition, const Offset(50, 50));
|
||||||
|
expect(down.position, center);
|
||||||
|
expect(down.delta, Offset.zero);
|
||||||
|
expect(down.localDelta, Offset.zero);
|
||||||
|
expect(down.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(move.localPosition, const Offset(50, 50) + moved / scaleFactor);
|
||||||
|
expect(move.position, center + moved);
|
||||||
|
expect(move.delta, moved);
|
||||||
|
expect(move.localDelta, moved / scaleFactor);
|
||||||
|
expect(move.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(up.localPosition, const Offset(50, 50) + moved / scaleFactor);
|
||||||
|
expect(up.position, center + moved);
|
||||||
|
expect(up.delta, Offset.zero);
|
||||||
|
expect(up.localDelta, Offset.zero);
|
||||||
|
expect(up.transform, expectedTransform);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
await scrollAt(center, tester);
|
||||||
|
expect(events.single.localPosition, const Offset(50, 50));
|
||||||
|
expect(events.single.position, center);
|
||||||
|
expect(events.single.delta, Offset.zero);
|
||||||
|
expect(events.single.localDelta, Offset.zero);
|
||||||
|
expect(events.single.transform, expectedTransform);
|
||||||
|
|
||||||
|
await gesture.removePointer();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('rotated for touch/signal', (WidgetTester tester) async {
|
||||||
|
final List<PointerEvent> events = <PointerEvent>[];
|
||||||
|
final Key key = UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Transform(
|
||||||
|
transform: Matrix4.identity()
|
||||||
|
..rotateZ(math.pi / 2), // 90 degrees clockwise around Container origin
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (PointerDownEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerUp: (PointerUpEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerMove: (PointerMoveEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
onPointerSignal: (PointerSignalEvent event) {
|
||||||
|
events.add(event);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
key: key,
|
||||||
|
color: Colors.red,
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const Offset moved = Offset(20, 30);
|
||||||
|
final Offset downPosition = tester.getCenter(find.byKey(key)) + const Offset(10, 5);
|
||||||
|
final TestGesture gesture = await tester.startGesture(downPosition);
|
||||||
|
await gesture.moveBy(moved);
|
||||||
|
await gesture.up();
|
||||||
|
|
||||||
|
expect(events, hasLength(3));
|
||||||
|
final PointerDownEvent down = events[0];
|
||||||
|
final PointerMoveEvent move = events[1];
|
||||||
|
final PointerUpEvent up = events[2];
|
||||||
|
|
||||||
|
const Offset offset = Offset((800 - 100) / 2, (600 - 100) / 2);
|
||||||
|
final Matrix4 expectedTransform = Matrix4.identity()
|
||||||
|
..rotateZ(-math.pi / 2)
|
||||||
|
..translate(-offset.dx, -offset.dy, 0.0);
|
||||||
|
|
||||||
|
final Offset localDownPosition = const Offset(50, 50) + const Offset(5, -10);
|
||||||
|
expect(down.localPosition, within(distance: 0.001, from: localDownPosition));
|
||||||
|
expect(down.position, downPosition);
|
||||||
|
expect(down.delta, Offset.zero);
|
||||||
|
expect(down.localDelta, Offset.zero);
|
||||||
|
expect(down.transform, expectedTransform);
|
||||||
|
|
||||||
|
const Offset localDelta = Offset(30, -20);
|
||||||
|
expect(move.localPosition, within(distance: 0.001, from: localDownPosition + localDelta));
|
||||||
|
expect(move.position, downPosition + moved);
|
||||||
|
expect(move.delta, moved);
|
||||||
|
expect(move.localDelta, localDelta);
|
||||||
|
expect(move.transform, expectedTransform);
|
||||||
|
|
||||||
|
expect(up.localPosition, within(distance: 0.001, from: localDownPosition + localDelta));
|
||||||
|
expect(up.position, downPosition + moved);
|
||||||
|
expect(up.delta, Offset.zero);
|
||||||
|
expect(up.localDelta, Offset.zero);
|
||||||
|
expect(up.transform, expectedTransform);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
await scrollAt(downPosition, tester);
|
||||||
|
expect(events.single.localPosition, within(distance: 0.001, from: localDownPosition));
|
||||||
|
expect(events.single.position, downPosition);
|
||||||
|
expect(events.single.delta, Offset.zero);
|
||||||
|
expect(events.single.localDelta, Offset.zero);
|
||||||
|
expect(events.single.transform, expectedTransform);
|
||||||
|
|
||||||
|
await gesture.removePointer();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> scrollAt(Offset position, WidgetTester tester) {
|
||||||
|
final TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);
|
||||||
|
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
||||||
|
testPointer.hover(position);
|
||||||
|
final HitTestResult result = tester.hitTestOnBinding(position);
|
||||||
|
return tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
|
||||||
}
|
}
|
||||||
|
218
packages/flutter/test/widgets/transformed_scrollable_test.dart
Normal file
218
packages/flutter/test/widgets/transformed_scrollable_test.dart
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// Copyright 2019 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:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Scrollable scaled up', (WidgetTester tester) async {
|
||||||
|
final ScrollController controller = ScrollController();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Transform.scale(
|
||||||
|
scale: 2.0,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
cacheExtent: 0.0,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Container(
|
||||||
|
height: 100.0,
|
||||||
|
color: index % 2 == 0 ? Colors.blue : Colors.red,
|
||||||
|
child: Text('Tile $index'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(0.0, -100.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 50.0); // 100.0 / 2.0
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(80.0, -70.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 85.0); // 50.0 + (70.0 / 2)
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(100.0, 0.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 85.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(0.0, 85.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 42.5); // 85.0 - (85.0 / 2)
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Scrollable scaled down', (WidgetTester tester) async {
|
||||||
|
final ScrollController controller = ScrollController();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
cacheExtent: 0.0,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Container(
|
||||||
|
height: 100.0,
|
||||||
|
color: index % 2 == 0 ? Colors.blue : Colors.red,
|
||||||
|
child: Text('Tile $index'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(0.0, -100.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 200.0); // 100.0 * 2.0
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(80.0, -70.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 340.0); // 200.0 + (70.0 * 2)
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(100.0, 0.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 340.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(0.0, 170.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 0.0); // 340.0 - (170.0 * 2)
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Scrollable rotated 90 degrees', (WidgetTester tester) async {
|
||||||
|
final ScrollController controller = ScrollController();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Transform.rotate(
|
||||||
|
angle: math.pi / 2,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
cacheExtent: 0.0,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Container(
|
||||||
|
height: 100.0,
|
||||||
|
color: index % 2 == 0 ? Colors.blue : Colors.red,
|
||||||
|
child: Text('Tile $index'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(100.0, 0.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 100.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(0.0, -100.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 100.0);
|
||||||
|
|
||||||
|
await tester.drag(find.byType(ListView), const Offset(-70.0, -50.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.offset, 30.0); // 100.0 - 70.0
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Perspective transform on scrollable', (WidgetTester tester) async {
|
||||||
|
final ScrollController controller = ScrollController();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Transform(
|
||||||
|
transform: Matrix4.identity()
|
||||||
|
..setEntry(3, 2, 0.001)
|
||||||
|
..rotateX(math.pi / 4),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
cacheExtent: 0.0,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Container(
|
||||||
|
height: 100.0,
|
||||||
|
color: index % 2 == 0 ? Colors.blue : Colors.red,
|
||||||
|
child: Text('Tile $index'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
|
||||||
|
// We want to test that the point in the ListView that the finger touches
|
||||||
|
// on the screen stays under the finger as the finger scrolls the ListView
|
||||||
|
// in vertical direction. For this, we pick a point in the ListView (here
|
||||||
|
// the center of Tile 5) and calculate its position in the coordinate space
|
||||||
|
// of the screen. We then place our finger on that point and drag that
|
||||||
|
// point up in vertical direction. After the scroll activity is done,
|
||||||
|
// we verify that - in the coordinate space of the screen (!) - the point
|
||||||
|
// has moved the same distance as the finger. Due to the perspective
|
||||||
|
// transform the point will have moved more distance in the *local*
|
||||||
|
// coordinate system of the ListView.
|
||||||
|
|
||||||
|
// Calculate where the center of Tile 5 is located in the coordinate
|
||||||
|
// space of the screen. We cannot use `tester.getCenter` because it
|
||||||
|
// does not properly remove the perspective component from the transform
|
||||||
|
// to give us the place on the screen at which we need to touch the screen
|
||||||
|
// to have the center of Tile 5 directly under our finger.
|
||||||
|
final RenderBox tile5 = tester.renderObject(find.text('Tile 5'));
|
||||||
|
final Offset pointOnScreenStart = MatrixUtils.transformPoint(
|
||||||
|
PointerEvent.removePerspectiveTransform(tile5.getTransformTo(null)),
|
||||||
|
tile5.size.center(Offset.zero),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Place the finger on the tracked point and move the finger upwards for
|
||||||
|
// 50 pixels to scroll the ListView (the ListView's scroll offset will
|
||||||
|
// move more then 50 pixels due to the perspective transform).
|
||||||
|
await tester.dragFrom(pointOnScreenStart, const Offset(0.0, -50.0));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Get the new position of the tracked point in the screen's coordinate
|
||||||
|
// system.
|
||||||
|
final Offset pointOnScreenEnd = MatrixUtils.transformPoint(
|
||||||
|
PointerEvent.removePerspectiveTransform(tile5.getTransformTo(null)),
|
||||||
|
tile5.size.center(Offset.zero),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The tracked point (in the coordinate space of the screen) and the finger
|
||||||
|
// should have moved the same vertical distance over the screen.
|
||||||
|
expect(
|
||||||
|
pointOnScreenStart.dy - pointOnScreenEnd.dy,
|
||||||
|
within(distance: 0.00001, from: 50.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// While the point traveled the same distance as the finger in the
|
||||||
|
// coordinate space of the screen, the scroll view actually moved far more
|
||||||
|
// pixels in its local coordinate system due to the perspective transform.
|
||||||
|
expect(controller.offset, greaterThan(100));
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user