1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'package:flutter/gestures.dart';
8import 'package:flutter/material.dart';
9import 'package:flutter_test/flutter_test.dart';
10
11void main() {
12 group('Horizontal', () {
13 testWidgets('gets local coordinates', (WidgetTester tester) async {
14 int dragCancelCount = 0;
15 final List<DragDownDetails> downDetails = <DragDownDetails>[];
16 final List<DragEndDetails> endDetails = <DragEndDetails>[];
17 final List<DragStartDetails> startDetails = <DragStartDetails>[];
18 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
19
20 final Key redContainer = UniqueKey();
21 await tester.pumpWidget(
22 Center(
23 child: GestureDetector(
24 onHorizontalDragCancel: () {
25 dragCancelCount++;
26 },
27 onHorizontalDragDown: (DragDownDetails details) {
28 downDetails.add(details);
29 },
30 onHorizontalDragEnd: (DragEndDetails details) {
31 endDetails.add(details);
32 },
33 onHorizontalDragStart: (DragStartDetails details) {
34 startDetails.add(details);
35 },
36 onHorizontalDragUpdate: (DragUpdateDetails details) {
37 updateDetails.add(details);
38 },
39 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
40 ),
41 ),
42 );
43
44 await tester.drag(find.byKey(redContainer), const Offset(100, 0));
45 expect(dragCancelCount, 0);
46 expect(downDetails.single.localPosition, const Offset(50, 75));
47 expect(downDetails.single.globalPosition, const Offset(400, 300));
48 expect(endDetails, hasLength(1));
49 expect(startDetails.single.localPosition, const Offset(50, 75));
50 expect(startDetails.single.globalPosition, const Offset(400, 300));
51 expect(updateDetails.last.localPosition, const Offset(50 + 100.0, 75));
52 expect(updateDetails.last.globalPosition, const Offset(400 + 100.0, 300));
53 expect(
54 updateDetails.fold(
55 Offset.zero,
56 (Offset offset, DragUpdateDetails details) => offset + details.delta,
57 ),
58 const Offset(100, 0),
59 );
60 expect(
61 updateDetails.fold(
62 0.0,
63 (double offset, DragUpdateDetails details) => offset + (details.primaryDelta ?? 0),
64 ),
65 100.0,
66 );
67 });
68
69 testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (
70 WidgetTester tester,
71 ) async {
72 int dragCancelCount = 0;
73 final List<DragDownDetails> downDetails = <DragDownDetails>[];
74 final List<DragEndDetails> endDetails = <DragEndDetails>[];
75 final List<DragStartDetails> startDetails = <DragStartDetails>[];
76 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
77
78 final Key redContainer = UniqueKey();
79 await tester.pumpWidget(
80 Center(
81 child: Transform.scale(
82 scale: 2.0,
83 child: GestureDetector(
84 onHorizontalDragCancel: () {
85 dragCancelCount++;
86 },
87 onHorizontalDragDown: (DragDownDetails details) {
88 downDetails.add(details);
89 },
90 onHorizontalDragEnd: (DragEndDetails details) {
91 endDetails.add(details);
92 },
93 onHorizontalDragStart: (DragStartDetails details) {
94 startDetails.add(details);
95 },
96 onHorizontalDragUpdate: (DragUpdateDetails details) {
97 updateDetails.add(details);
98 },
99 onTap: () {
100 // Competing gesture detector.
101 },
102 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
103 ),
104 ),
105 ),
106 );
107
108 // Move just above kTouchSlop should recognize drag.
109 await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop + 1, 0));
110
111 expect(dragCancelCount, 0);
112 expect(downDetails.single.localPosition, const Offset(50, 75));
113 expect(downDetails.single.globalPosition, const Offset(400, 300));
114 expect(endDetails, hasLength(1));
115 expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) / 2, 75));
116 expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300));
117 expect(updateDetails, isEmpty);
118
119 dragCancelCount = 0;
120 downDetails.clear();
121 endDetails.clear();
122 startDetails.clear();
123 updateDetails.clear();
124
125 // Move just below kTouchSlop does not recognize drag.
126 await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop - 1, 0));
127 expect(dragCancelCount, 1);
128 expect(downDetails.single.localPosition, const Offset(50, 75));
129 expect(downDetails.single.globalPosition, const Offset(400, 300));
130 expect(endDetails, isEmpty);
131 expect(startDetails, isEmpty);
132 expect(updateDetails, isEmpty);
133
134 dragCancelCount = 0;
135 downDetails.clear();
136 endDetails.clear();
137 startDetails.clear();
138 updateDetails.clear();
139
140 // Move in two separate movements
141 final TestGesture gesture = await tester.startGesture(
142 tester.getCenter(find.byKey(redContainer)),
143 );
144 await gesture.moveBy(const Offset(kTouchSlop + 1, 30));
145 await gesture.moveBy(const Offset(100, 10));
146 await gesture.up();
147
148 expect(dragCancelCount, 0);
149 expect(downDetails.single.localPosition, const Offset(50, 75));
150 expect(downDetails.single.globalPosition, const Offset(400, 300));
151 expect(endDetails, hasLength(1));
152 expect(
153 startDetails.single.localPosition,
154 const Offset(50 + (kTouchSlop + 1) / 2, 75.0 + 30.0 / 2),
155 );
156 expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300 + 30.0));
157 expect(
158 updateDetails.single.localPosition,
159 startDetails.single.localPosition + const Offset(100.0 / 2, 10 / 2),
160 );
161 expect(
162 updateDetails.single.globalPosition,
163 startDetails.single.globalPosition + const Offset(100.0, 10.0),
164 );
165 expect(updateDetails.single.delta, const Offset(100.0 / 2, 0.0));
166 expect(updateDetails.single.primaryDelta, 100.0 / 2);
167
168 dragCancelCount = 0;
169 downDetails.clear();
170 endDetails.clear();
171 startDetails.clear();
172 updateDetails.clear();
173 });
174
175 testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (
176 WidgetTester tester,
177 ) async {
178 int dragCancelCount = 0;
179 final List<DragDownDetails> downDetails = <DragDownDetails>[];
180 final List<DragEndDetails> endDetails = <DragEndDetails>[];
181 final List<DragStartDetails> startDetails = <DragStartDetails>[];
182 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
183
184 final Key redContainer = UniqueKey();
185 await tester.pumpWidget(
186 Center(
187 child: Transform.scale(
188 scale: 0.5,
189 child: GestureDetector(
190 onHorizontalDragCancel: () {
191 dragCancelCount++;
192 },
193 onHorizontalDragDown: (DragDownDetails details) {
194 downDetails.add(details);
195 },
196 onHorizontalDragEnd: (DragEndDetails details) {
197 endDetails.add(details);
198 },
199 onHorizontalDragStart: (DragStartDetails details) {
200 startDetails.add(details);
201 },
202 onHorizontalDragUpdate: (DragUpdateDetails details) {
203 updateDetails.add(details);
204 },
205 onTap: () {
206 // Competing gesture detector.
207 },
208 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
209 ),
210 ),
211 ),
212 );
213
214 // Move just above kTouchSlop should recognize drag.
215 await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop + 1, 0));
216
217 expect(dragCancelCount, 0);
218 expect(downDetails.single.localPosition, const Offset(50, 75));
219 expect(downDetails.single.globalPosition, const Offset(400, 300));
220 expect(endDetails, hasLength(1));
221 expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) * 2, 75));
222 expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300));
223 expect(updateDetails, isEmpty);
224
225 dragCancelCount = 0;
226 downDetails.clear();
227 endDetails.clear();
228 startDetails.clear();
229 updateDetails.clear();
230
231 // Move just below kTouchSlop does not recognize drag.
232 await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop - 1, 0));
233 expect(dragCancelCount, 1);
234 expect(downDetails.single.localPosition, const Offset(50, 75));
235 expect(downDetails.single.globalPosition, const Offset(400, 300));
236 expect(endDetails, isEmpty);
237 expect(startDetails, isEmpty);
238 expect(updateDetails, isEmpty);
239
240 dragCancelCount = 0;
241 downDetails.clear();
242 endDetails.clear();
243 startDetails.clear();
244 updateDetails.clear();
245
246 // Move in two separate movements
247 final TestGesture gesture = await tester.startGesture(
248 tester.getCenter(find.byKey(redContainer)),
249 );
250 await gesture.moveBy(const Offset(kTouchSlop + 1, 30));
251 await gesture.moveBy(const Offset(100, 10));
252 await gesture.up();
253
254 expect(dragCancelCount, 0);
255 expect(downDetails.single.localPosition, const Offset(50, 75));
256 expect(downDetails.single.globalPosition, const Offset(400, 300));
257 expect(endDetails, hasLength(1));
258 expect(
259 startDetails.single.localPosition,
260 const Offset(50 + (kTouchSlop + 1) * 2, 75.0 + 30.0 * 2),
261 );
262 expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300 + 30.0));
263 expect(
264 updateDetails.single.localPosition,
265 startDetails.single.localPosition + const Offset(100.0 * 2, 10.0 * 2.0),
266 );
267 expect(
268 updateDetails.single.globalPosition,
269 startDetails.single.globalPosition + const Offset(100.0, 10.0),
270 );
271 expect(updateDetails.single.delta, const Offset(100.0 * 2.0, 0.0));
272 expect(updateDetails.single.primaryDelta, 100.0 * 2);
273
274 dragCancelCount = 0;
275 downDetails.clear();
276 endDetails.clear();
277 startDetails.clear();
278 updateDetails.clear();
279 });
280
281 testWidgets('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (
282 WidgetTester tester,
283 ) async {
284 int dragCancelCount = 0;
285 final List<DragDownDetails> downDetails = <DragDownDetails>[];
286 final List<DragEndDetails> endDetails = <DragEndDetails>[];
287 final List<DragStartDetails> startDetails = <DragStartDetails>[];
288 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
289
290 final Key redContainer = UniqueKey();
291 await tester.pumpWidget(
292 Center(
293 child: Transform.rotate(
294 angle: math.pi / 4,
295 child: GestureDetector(
296 onHorizontalDragCancel: () {
297 dragCancelCount++;
298 },
299 onHorizontalDragDown: (DragDownDetails details) {
300 downDetails.add(details);
301 },
302 onHorizontalDragEnd: (DragEndDetails details) {
303 endDetails.add(details);
304 },
305 onHorizontalDragStart: (DragStartDetails details) {
306 startDetails.add(details);
307 },
308 onHorizontalDragUpdate: (DragUpdateDetails details) {
309 updateDetails.add(details);
310 },
311 onTap: () {
312 // Competing gesture detector.
313 },
314 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
315 ),
316 ),
317 ),
318 );
319
320 // Move just below kTouchSlop should not recognize drag.
321 const Offset moveBy1 = Offset(kTouchSlop / 2, kTouchSlop / 2);
322 expect(moveBy1.distance, lessThan(kTouchSlop));
323 await tester.drag(find.byKey(redContainer), moveBy1);
324 expect(dragCancelCount, 1);
325 expect(
326 downDetails.single.localPosition,
327 within(distance: 0.0001, from: const Offset(50, 75)),
328 );
329 expect(
330 downDetails.single.globalPosition,
331 within(distance: 0.0001, from: const Offset(400, 300)),
332 );
333 expect(endDetails, isEmpty);
334 expect(startDetails, isEmpty);
335 expect(updateDetails, isEmpty);
336
337 dragCancelCount = 0;
338 downDetails.clear();
339 endDetails.clear();
340 startDetails.clear();
341 updateDetails.clear();
342
343 // Move above kTouchSlop recognizes drag.
344 final TestGesture gesture = await tester.startGesture(
345 tester.getCenter(find.byKey(redContainer)),
346 );
347 await gesture.moveBy(const Offset(kTouchSlop, kTouchSlop));
348 await gesture.moveBy(const Offset(3, 4));
349 await gesture.up();
350
351 expect(dragCancelCount, 0);
352 expect(
353 downDetails.single.localPosition,
354 within(distance: 0.0001, from: const Offset(50, 75)),
355 );
356 expect(
357 downDetails.single.globalPosition,
358 within(distance: 0.0001, from: const Offset(400, 300)),
359 );
360 expect(endDetails, hasLength(1));
361 expect(startDetails, hasLength(1));
362 expect(
363 updateDetails.single.globalPosition,
364 within(distance: 0.0001, from: const Offset(400 + kTouchSlop + 3, 300 + kTouchSlop + 4)),
365 );
366 expect(
367 updateDetails.single.delta,
368 within(distance: 0.1, from: const Offset(5, 0.0)),
369 ); // sqrt(3^2 + 4^2)
370 expect(
371 updateDetails.single.primaryDelta,
372 within<double>(distance: 0.1, from: 5.0),
373 ); // sqrt(3^2 + 4^2)
374 });
375 });
376
377 group('Vertical', () {
378 testWidgets('gets local coordinates', (WidgetTester tester) async {
379 int dragCancelCount = 0;
380 final List<DragDownDetails> downDetails = <DragDownDetails>[];
381 final List<DragEndDetails> endDetails = <DragEndDetails>[];
382 final List<DragStartDetails> startDetails = <DragStartDetails>[];
383 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
384
385 final Key redContainer = UniqueKey();
386 await tester.pumpWidget(
387 Center(
388 child: GestureDetector(
389 onVerticalDragCancel: () {
390 dragCancelCount++;
391 },
392 onVerticalDragDown: (DragDownDetails details) {
393 downDetails.add(details);
394 },
395 onVerticalDragEnd: (DragEndDetails details) {
396 endDetails.add(details);
397 },
398 onVerticalDragStart: (DragStartDetails details) {
399 startDetails.add(details);
400 },
401 onVerticalDragUpdate: (DragUpdateDetails details) {
402 updateDetails.add(details);
403 },
404 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
405 ),
406 ),
407 );
408
409 await tester.drag(find.byKey(redContainer), const Offset(0, 100));
410 expect(dragCancelCount, 0);
411 expect(downDetails.single.localPosition, const Offset(50, 75));
412 expect(downDetails.single.globalPosition, const Offset(400, 300));
413 expect(endDetails, hasLength(1));
414 expect(startDetails.single.localPosition, const Offset(50, 75));
415 expect(startDetails.single.globalPosition, const Offset(400, 300));
416 expect(updateDetails.last.localPosition, const Offset(50, 75 + 100.0));
417 expect(updateDetails.last.globalPosition, const Offset(400, 300 + 100.0));
418 expect(
419 updateDetails.fold(
420 Offset.zero,
421 (Offset offset, DragUpdateDetails details) => offset + details.delta,
422 ),
423 const Offset(0, 100),
424 );
425 expect(
426 updateDetails.fold(
427 0.0,
428 (double offset, DragUpdateDetails details) => offset + (details.primaryDelta ?? 0),
429 ),
430 100.0,
431 );
432 });
433
434 testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (
435 WidgetTester tester,
436 ) async {
437 int dragCancelCount = 0;
438 final List<DragDownDetails> downDetails = <DragDownDetails>[];
439 final List<DragEndDetails> endDetails = <DragEndDetails>[];
440 final List<DragStartDetails> startDetails = <DragStartDetails>[];
441 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
442
443 final Key redContainer = UniqueKey();
444 await tester.pumpWidget(
445 Center(
446 child: Transform.scale(
447 scale: 2.0,
448 child: GestureDetector(
449 onVerticalDragCancel: () {
450 dragCancelCount++;
451 },
452 onVerticalDragDown: (DragDownDetails details) {
453 downDetails.add(details);
454 },
455 onVerticalDragEnd: (DragEndDetails details) {
456 endDetails.add(details);
457 },
458 onVerticalDragStart: (DragStartDetails details) {
459 startDetails.add(details);
460 },
461 onVerticalDragUpdate: (DragUpdateDetails details) {
462 updateDetails.add(details);
463 },
464 onTap: () {
465 // Competing gesture detector.
466 },
467 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
468 ),
469 ),
470 ),
471 );
472
473 // Move just above kTouchSlop should recognize drag.
474 await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop + 1));
475
476 expect(dragCancelCount, 0);
477 expect(downDetails.single.localPosition, const Offset(50, 75));
478 expect(downDetails.single.globalPosition, const Offset(400, 300));
479 expect(endDetails, hasLength(1));
480 expect(startDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop + 1) / 2));
481 expect(startDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop + 1)));
482 expect(updateDetails, isEmpty);
483
484 dragCancelCount = 0;
485 downDetails.clear();
486 endDetails.clear();
487 startDetails.clear();
488 updateDetails.clear();
489
490 // Move just below kTouchSlop does not recognize drag.
491 await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop - 1));
492 expect(dragCancelCount, 1);
493 expect(downDetails.single.localPosition, const Offset(50, 75));
494 expect(downDetails.single.globalPosition, const Offset(400, 300));
495 expect(endDetails, isEmpty);
496 expect(startDetails, isEmpty);
497 expect(updateDetails, isEmpty);
498
499 dragCancelCount = 0;
500 downDetails.clear();
501 endDetails.clear();
502 startDetails.clear();
503 updateDetails.clear();
504
505 // Move in two separate movements
506 final TestGesture gesture = await tester.startGesture(
507 tester.getCenter(find.byKey(redContainer)),
508 );
509 await gesture.moveBy(const Offset(30, kTouchSlop + 1));
510 await gesture.moveBy(const Offset(10, 100));
511 await gesture.up();
512
513 expect(dragCancelCount, 0);
514 expect(downDetails.single.localPosition, const Offset(50, 75));
515 expect(downDetails.single.globalPosition, const Offset(400, 300));
516 expect(endDetails, hasLength(1));
517 expect(
518 startDetails.single.localPosition,
519 const Offset(50 + 30.0 / 2, 75.0 + (kTouchSlop + 1) / 2),
520 );
521 expect(startDetails.single.globalPosition, const Offset(400 + 30.0, 300 + (kTouchSlop + 1)));
522 expect(
523 updateDetails.single.localPosition,
524 startDetails.single.localPosition + const Offset(10.0 / 2, 100.0 / 2),
525 );
526 expect(
527 updateDetails.single.globalPosition,
528 startDetails.single.globalPosition + const Offset(10.0, 100.0),
529 );
530 expect(updateDetails.single.delta, const Offset(0.0, 100.0 / 2));
531 expect(updateDetails.single.primaryDelta, 100.0 / 2);
532
533 dragCancelCount = 0;
534 downDetails.clear();
535 endDetails.clear();
536 startDetails.clear();
537 updateDetails.clear();
538 });
539
540 testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (
541 WidgetTester tester,
542 ) async {
543 int dragCancelCount = 0;
544 final List<DragDownDetails> downDetails = <DragDownDetails>[];
545 final List<DragEndDetails> endDetails = <DragEndDetails>[];
546 final List<DragStartDetails> startDetails = <DragStartDetails>[];
547 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
548
549 final Key redContainer = UniqueKey();
550 await tester.pumpWidget(
551 Center(
552 child: Transform.scale(
553 scale: 0.5,
554 child: GestureDetector(
555 onVerticalDragCancel: () {
556 dragCancelCount++;
557 },
558 onVerticalDragDown: (DragDownDetails details) {
559 downDetails.add(details);
560 },
561 onVerticalDragEnd: (DragEndDetails details) {
562 endDetails.add(details);
563 },
564 onVerticalDragStart: (DragStartDetails details) {
565 startDetails.add(details);
566 },
567 onVerticalDragUpdate: (DragUpdateDetails details) {
568 updateDetails.add(details);
569 },
570 onTap: () {
571 // Competing gesture detector.
572 },
573 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
574 ),
575 ),
576 ),
577 );
578
579 // Move just above kTouchSlop should recognize drag.
580 await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop + 1));
581
582 expect(dragCancelCount, 0);
583 expect(downDetails.single.localPosition, const Offset(50, 75));
584 expect(downDetails.single.globalPosition, const Offset(400, 300));
585 expect(endDetails, hasLength(1));
586 expect(startDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop + 1) * 2));
587 expect(startDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop + 1)));
588 expect(updateDetails, isEmpty);
589
590 dragCancelCount = 0;
591 downDetails.clear();
592 endDetails.clear();
593 startDetails.clear();
594 updateDetails.clear();
595
596 // Move just below kTouchSlop does not recognize drag.
597 await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop - 1));
598 expect(dragCancelCount, 1);
599 expect(downDetails.single.localPosition, const Offset(50, 75));
600 expect(downDetails.single.globalPosition, const Offset(400, 300));
601 expect(endDetails, isEmpty);
602 expect(startDetails, isEmpty);
603 expect(updateDetails, isEmpty);
604
605 dragCancelCount = 0;
606 downDetails.clear();
607 endDetails.clear();
608 startDetails.clear();
609 updateDetails.clear();
610
611 // Move in two separate movements
612 final TestGesture gesture = await tester.startGesture(
613 tester.getCenter(find.byKey(redContainer)),
614 );
615 await gesture.moveBy(const Offset(30, kTouchSlop + 1));
616 await gesture.moveBy(const Offset(10, 100));
617 await gesture.up();
618
619 expect(dragCancelCount, 0);
620 expect(downDetails.single.localPosition, const Offset(50, 75));
621 expect(downDetails.single.globalPosition, const Offset(400, 300));
622 expect(endDetails, hasLength(1));
623 expect(
624 startDetails.single.localPosition,
625 const Offset(50 + 30.0 * 2, 75.0 + (kTouchSlop + 1) * 2),
626 );
627 expect(startDetails.single.globalPosition, const Offset(400 + 30.0, 300 + (kTouchSlop + 1)));
628 expect(
629 updateDetails.single.localPosition,
630 startDetails.single.localPosition + const Offset(10.0 * 2, 100.0 * 2.0),
631 );
632 expect(
633 updateDetails.single.globalPosition,
634 startDetails.single.globalPosition + const Offset(10.0, 100.0),
635 );
636 expect(updateDetails.single.delta, const Offset(0.0, 100.0 * 2.0));
637 expect(updateDetails.single.primaryDelta, 100.0 * 2);
638
639 dragCancelCount = 0;
640 downDetails.clear();
641 endDetails.clear();
642 startDetails.clear();
643 updateDetails.clear();
644 });
645
646 testWidgets('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (
647 WidgetTester tester,
648 ) async {
649 int dragCancelCount = 0;
650 final List<DragDownDetails> downDetails = <DragDownDetails>[];
651 final List<DragEndDetails> endDetails = <DragEndDetails>[];
652 final List<DragStartDetails> startDetails = <DragStartDetails>[];
653 final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];
654
655 final Key redContainer = UniqueKey();
656 await tester.pumpWidget(
657 Center(
658 child: Transform.rotate(
659 angle: math.pi / 4,
660 child: GestureDetector(
661 onVerticalDragCancel: () {
662 dragCancelCount++;
663 },
664 onVerticalDragDown: (DragDownDetails details) {
665 downDetails.add(details);
666 },
667 onVerticalDragEnd: (DragEndDetails details) {
668 endDetails.add(details);
669 },
670 onVerticalDragStart: (DragStartDetails details) {
671 startDetails.add(details);
672 },
673 onVerticalDragUpdate: (DragUpdateDetails details) {
674 updateDetails.add(details);
675 },
676 onTap: () {
677 // Competing gesture detector.
678 },
679 child: Container(key: redContainer, width: 100, height: 150, color: Colors.red),
680 ),
681 ),
682 ),
683 );
684
685 // Move just below kTouchSlop should not recognize drag.
686 const Offset moveBy1 = Offset(kTouchSlop / 2, kTouchSlop / 2);
687 expect(moveBy1.distance, lessThan(kTouchSlop));
688 await tester.drag(find.byKey(redContainer), moveBy1);
689 expect(dragCancelCount, 1);
690 expect(
691 downDetails.single.localPosition,
692 within(distance: 0.0001, from: const Offset(50, 75)),
693 );
694 expect(
695 downDetails.single.globalPosition,
696 within(distance: 0.0001, from: const Offset(400, 300)),
697 );
698 expect(endDetails, isEmpty);
699 expect(startDetails, isEmpty);
700 expect(updateDetails, isEmpty);
701
702 dragCancelCount = 0;
703 downDetails.clear();
704 endDetails.clear();
705 startDetails.clear();
706 updateDetails.clear();
707
708 // Move above kTouchSlop recognizes drag.
709 final TestGesture gesture = await tester.startGesture(
710 tester.getCenter(find.byKey(redContainer)),
711 );
712 await gesture.moveBy(const Offset(kTouchSlop, kTouchSlop));
713 await gesture.moveBy(const Offset(-4, 3));
714 await gesture.up();
715
716 expect(dragCancelCount, 0);
717 expect(
718 downDetails.single.localPosition,
719 within(distance: 0.0001, from: const Offset(50, 75)),
720 );
721 expect(
722 downDetails.single.globalPosition,
723 within(distance: 0.0001, from: const Offset(400, 300)),
724 );
725 expect(endDetails, hasLength(1));
726 expect(startDetails, hasLength(1));
727 expect(
728 updateDetails.single.globalPosition,
729 within(distance: 0.0001, from: const Offset(400 + kTouchSlop - 4, 300 + kTouchSlop + 3)),
730 );
731 expect(
732 updateDetails.single.delta,
733 within(distance: 0.1, from: const Offset(0.0, 5.0)),
734 ); // sqrt(3^2 + 4^2)
735 expect(
736 updateDetails.single.primaryDelta,
737 within<double>(distance: 0.1, from: 5.0),
738 ); // sqrt(3^2 + 4^2)
739 });
740 });
741}
742