add PointerDeviceKind to ScaleStartDetails (#165096)

Adds the `PointerDeviceKind` of the scale start event to
`ScaleStartDetails`, which is currently missing.

This is a bug according to #115061. Additionally, according to the docs:

>Having both a pan gesture recognizer and a scale gesture recognizer is
redundant; scale is a superset of pan. Just use the scale gesture
recognizer.

See also #135936 for similar issues and the PRs fixing them.

If multiple pointers are contacting the screen at scale start, then the
`PointerDeviceKind` of the first pointer is used. In practice this
should be good enough as I don't know of any UI that expects you to use
two different pointer kinds at the same time. However, if this is an
issue, we could either give a list of pointers with their kinds or a set
of all active `PointerDeviceKind`s. I don't think this is necessary
though.

Closes #115061.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
yakagami
2025-03-27 14:44:17 -04:00
committed by GitHub
parent 16fc684886
commit 479c4bb224
2 changed files with 100 additions and 0 deletions

View File

@@ -99,6 +99,7 @@ class ScaleStartDetails {
Offset? localFocalPoint,
this.pointerCount = 0,
this.sourceTimeStamp,
this.kind,
}) : localFocalPoint = localFocalPoint ?? focalPoint;
/// The initial focal point of the pointers in contact with the screen.
@@ -134,6 +135,12 @@ class ScaleStartDetails {
/// Could be null if triggered from proxied events such as accessibility.
final Duration? sourceTimeStamp;
/// The kind of the device that initiated the event.
///
/// If multiple pointers are touching the screen, the kind of the pointer
/// device that first initiated the event is used.
final PointerDeviceKind? kind;
@override
String toString() =>
'ScaleStartDetails(focalPoint: $focalPoint, localFocalPoint: $localFocalPoint, pointersCount: $pointerCount)';
@@ -771,6 +778,12 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
localFocalPoint: _localFocalPoint,
pointerCount: pointerCount,
sourceTimeStamp: _initialEventTimestamp,
kind:
_pointerQueue.isNotEmpty
? getKindForPointer(_pointerQueue.first)
: _pointerPanZooms.isNotEmpty
? getKindForPointer(_pointerPanZooms.keys.first)
: null,
),
);
});

View File

@@ -1830,4 +1830,91 @@ void main() {
tap.dispose();
},
);
testGesture('ScaleStartDetails should contain the correct PointerDeviceKind', (
GestureTester tester,
) {
final ScaleGestureRecognizer scale = ScaleGestureRecognizer();
bool didStartScale = false;
PointerDeviceKind? updatedKind;
scale.onStart = (ScaleStartDetails details) {
didStartScale = true;
updatedKind = details.kind;
};
scale.onEnd = (ScaleEndDetails details) {
didStartScale = false;
};
// The default kind is touch.
// ignore: avoid_redundant_argument_values
final TestPointer pointer1 = TestPointer(1, PointerDeviceKind.touch);
final PointerDownEvent down = pointer1.down(Offset.zero);
scale.addPointer(down);
tester.closeArena(1);
// One-finger panning
tester.route(down);
tester.route(pointer1.move(const Offset(20.0, 30.0)));
expect(didStartScale, isTrue);
expect(updatedKind, PointerDeviceKind.touch);
tester.route(pointer1.up());
expect(didStartScale, isFalse);
final TestPointer pointer2 = TestPointer(2, PointerDeviceKind.mouse);
final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 20.0));
scale.addPointer(down2);
tester.closeArena(2);
tester.route(down2);
tester.route(pointer2.move(const Offset(20.0, 30.0)));
expect(didStartScale, isTrue);
expect(updatedKind, PointerDeviceKind.mouse);
tester.route(pointer2.up());
expect(didStartScale, isFalse);
final TestPointer pointer3 = TestPointer(3, PointerDeviceKind.stylus);
final PointerDownEvent down3 = pointer3.down(const Offset(10.0, 20.0));
scale.addPointer(down3);
tester.closeArena(3);
tester.route(down3);
tester.route(pointer3.move(const Offset(20.0, 30.0)));
expect(didStartScale, isTrue);
expect(updatedKind, PointerDeviceKind.stylus);
tester.route(pointer3.up());
expect(didStartScale, isFalse);
final TestPointer pointer4 = TestPointer(4, PointerDeviceKind.invertedStylus);
final PointerDownEvent down4 = pointer4.down(const Offset(10.0, 20.0));
scale.addPointer(down4);
tester.closeArena(4);
tester.route(down4);
tester.route(pointer4.move(const Offset(20.0, 30.0)));
expect(didStartScale, isTrue);
expect(updatedKind, PointerDeviceKind.invertedStylus);
tester.route(pointer4.up());
expect(didStartScale, isFalse);
final TestPointer pointer5 = TestPointer(5, PointerDeviceKind.unknown);
final PointerDownEvent down5 = pointer5.down(const Offset(10.0, 20.0));
scale.addPointer(down5);
tester.closeArena(5);
tester.route(down5);
tester.route(pointer5.move(const Offset(20.0, 30.0)));
expect(didStartScale, isTrue);
expect(updatedKind, PointerDeviceKind.unknown);
tester.route(pointer5.up());
expect(didStartScale, isFalse);
final TestPointer pointer6 = TestPointer(6, PointerDeviceKind.trackpad);
final PointerPanZoomStartEvent down6 = pointer6.panZoomStart(const Offset(10.0, 20.0));
scale.addPointerPanZoom(down6);
tester.closeArena(6);
tester.route(down6);
tester.route(pointer6.panZoomUpdate(const Offset(20.0, 30.0)));
expect(didStartScale, isTrue);
expect(updatedKind, PointerDeviceKind.trackpad);
tester.route(pointer6.panZoomEnd());
expect(didStartScale, isFalse);
scale.dispose();
});
}