gesture_detector.dart 52.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/foundation.dart';
6 7
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
8

9 10
import 'basic.dart';
import 'framework.dart';
11

12
export 'package:flutter/gestures.dart' show
13 14 15 16
  DragDownDetails,
  DragStartDetails,
  DragUpdateDetails,
  DragEndDetails,
17 18
  GestureTapDownCallback,
  GestureTapUpCallback,
19
  GestureTapCallback,
20
  GestureTapCancelCallback,
21
  GestureLongPressCallback,
22 23 24 25
  GestureLongPressStartCallback,
  GestureLongPressMoveUpdateCallback,
  GestureLongPressUpCallback,
  GestureLongPressEndCallback,
26
  GestureDragDownCallback,
27 28 29
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
30
  GestureDragCancelCallback,
31 32
  GestureScaleStartCallback,
  GestureScaleUpdateCallback,
33
  GestureScaleEndCallback,
34 35 36 37
  GestureForcePressStartCallback,
  GestureForcePressPeakCallback,
  GestureForcePressEndCallback,
  GestureForcePressUpdateCallback,
38 39 40
  LongPressStartDetails,
  LongPressMoveUpdateDetails,
  LongPressEndDetails,
41 42 43
  ScaleStartDetails,
  ScaleUpdateDetails,
  ScaleEndDetails,
44 45
  TapDownDetails,
  TapUpDetails,
46
  ForcePressDetails,
47
  Velocity;
48
export 'package:flutter/rendering.dart' show RenderSemanticsGestureHandler;
49

50
// Examples can assume:
51
// // @dart = 2.9
52 53 54
// bool _lights;
// void setState(VoidCallback fn) { }
// String _last;
55
// Color _color;
56

57
/// Factory for creating gesture recognizers.
58
///
59
/// `T` is the type of gesture recognizer this class manages.
60 61
///
/// Used by [RawGestureDetector.gestures].
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
@optionalTypeArgs
abstract class GestureRecognizerFactory<T extends GestureRecognizer> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const GestureRecognizerFactory();

  /// Must return an instance of T.
  T constructor();

  /// Must configure the given instance (which will have been created by
  /// `constructor`).
  ///
  /// This normally means setting the callbacks.
  void initializer(T instance);

  bool _debugAssertTypeMatches(Type type) {
    assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.');
    return true;
  }
}

/// Signature for closures that implement [GestureRecognizerFactory.constructor].
84
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
85 86

/// Signature for closures that implement [GestureRecognizerFactory.initializer].
87
typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance);
88 89 90 91 92 93 94 95

/// Factory for creating gesture recognizers that delegates to callbacks.
///
/// Used by [RawGestureDetector.gestures].
class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> {
  /// Creates a gesture recognizer factory with the given callbacks.
  ///
  /// The arguments must not be null.
96 97 98
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
    : assert(_constructor != null),
      assert(_initializer != null);
99 100 101 102 103 104 105 106 107 108 109

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

  @override
  T constructor() => _constructor();

  @override
  void initializer(T instance) => _initializer(instance);
}
110

111 112
/// A widget that detects gestures.
///
113
/// Attempts to recognize gestures that correspond to its non-null callbacks.
114
///
115 116 117
/// If this widget has a child, it defers to that child for its sizing behavior.
/// If it does not have a child, it grows to fit the parent instead.
///
118 119 120
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
121 122 123 124
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
125
/// See <http://flutter.dev/gestures/> for additional information.
126 127 128 129
///
/// Material design applications typically react to touches with ink splash
/// effects. The [InkWell] class implements this effect and can be used in place
/// of a [GestureDetector] for handling taps.
130
///
131 132
/// {@animation 200 150 https://flutter.github.io/assets-for-api-docs/assets/widgets/gesture_detector.mp4}
///
133
/// {@tool snippet}
134
///
135 136 137
/// This example of a [Container] contains a black light bulb wrapped in a [GestureDetector].
/// It turns the light bulb yellow when the "turn lights on" button is tapped
/// by setting the `_lights` field. Above animation shows the code in use:
138 139
///
/// ```dart
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
/// Container(
///   alignment: FractionalOffset.center,
///   color: Colors.white,
///   child: Column(
///     mainAxisAlignment: MainAxisAlignment.center,
///     children: <Widget>[
///       Padding(
///         padding: const EdgeInsets.all(8.0),
///         child: Icon(
///           Icons.lightbulb_outline,
///           color: _lights ? Colors.yellow.shade600 : Colors.black,
///           size: 60,
///         ),
///       ),
///       GestureDetector(
///         onTap: () {
///           setState(() {
///             _lights = true;
///           });
///         },
///         child: Container(
///           color: Colors.yellow.shade600,
///           padding: const EdgeInsets.all(8),
///           child: const Text('TURN LIGHTS ON'),
///         ),
///       ),
///     ],
167 168 169
///   ),
/// )
/// ```
170
/// {@end-tool}
171
///
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
/// {@tool snippet}
///
/// This example of a [Container] wraps a [GestureDetector] widget.
/// Since the [GestureDetector] does not have a child it takes on the size of
/// its parent making the entire area of the surrounding [Container] clickable.
/// When tapped the [Container] turns yellow by setting the `_color` field:
///
/// ```dart
/// Container(
///   color: _color,
///   height: 200.0,
///   width: 200.0,
///   child: GestureDetector(
///     onTap: () {
///       setState(() {
///         _color = Colors.yellow;
///       });
///     },
///   ),
/// )
/// ```
/// {@end-tool}
///
195 196 197 198
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
199 200 201 202 203 204
///
/// See also:
///
///  * [Listener], a widget for listening to lower-level raw pointer events.
///  * [MouseRegion], a widget that tracks the movement of mice, even when no
///    button is pressed.
205
class GestureDetector extends StatelessWidget {
206 207 208 209 210 211 212 213 214 215 216
  /// Creates a widget that detects gestures.
  ///
  /// Pan and scale callbacks cannot be used simultaneously because scale is a
  /// superset of pan. Simply use the scale callbacks instead.
  ///
  /// Horizontal and vertical drag callbacks cannot be used simultaneously
  /// because a combination of a horizontal and vertical drag is a pan. Simply
  /// use the pan callbacks instead.
  ///
  /// By default, gesture detectors contribute semantic information to the tree
  /// that is used by assistive technology.
217
  GestureDetector({
218
    Key? key,
219
    this.child,
220
    this.onTapDown,
221 222
    this.onTapUp,
    this.onTap,
223
    this.onTapCancel,
224
    this.onSecondaryTap,
225 226 227
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,
228 229 230
    this.onTertiaryTapDown,
    this.onTertiaryTapUp,
    this.onTertiaryTapCancel,
231
    this.onDoubleTapDown,
232
    this.onDoubleTap,
233
    this.onDoubleTapCancel,
234
    this.onLongPress,
235 236
    this.onLongPressStart,
    this.onLongPressMoveUpdate,
237
    this.onLongPressUp,
238
    this.onLongPressEnd,
239 240 241 242 243
    this.onSecondaryLongPress,
    this.onSecondaryLongPressStart,
    this.onSecondaryLongPressMoveUpdate,
    this.onSecondaryLongPressUp,
    this.onSecondaryLongPressEnd,
244
    this.onVerticalDragDown,
245 246 247
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
248 249
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
250 251 252
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
253
    this.onHorizontalDragCancel,
254 255 256 257
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
258
    this.onPanDown,
259 260
    this.onPanStart,
    this.onPanUpdate,
261
    this.onPanEnd,
262
    this.onPanCancel,
263 264
    this.onScaleStart,
    this.onScaleUpdate,
265
    this.onScaleEnd,
Hixie's avatar
Hixie committed
266
    this.behavior,
267
    this.excludeFromSemantics = false,
268
    this.dragStartBehavior = DragStartBehavior.start,
269
  }) : assert(excludeFromSemantics != null),
270
       assert(dragStartBehavior != null),
271 272 273 274 275 276 277
       assert(() {
         final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
         final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
         final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
         final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
         if (havePan || haveScale) {
           if (havePan && haveScale) {
278 279 280 281 282 283 284
             throw FlutterError.fromParts(<DiagnosticsNode>[
               ErrorSummary('Incorrect GestureDetector arguments.'),
               ErrorDescription(
                 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.'
               ),
               ErrorHint('Just use the scale gesture recognizer.')
             ]);
285 286 287
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
288 289 290 291 292
             throw FlutterError(
               'Incorrect GestureDetector arguments.\n'
               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'
             );
293 294 295
           }
         }
         return true;
296
       }()),
297
       super(key: key);
298

299
  /// The widget below this widget in the tree.
300
  ///
301
  /// {@macro flutter.widgets.ProxyWidget.child}
302
  final Widget? child;
303

304 305
  /// A pointer that might cause a tap with a primary button has contacted the
  /// screen at a particular location.
306 307 308 309
  ///
  /// This is called after a short timeout, even if the winning gesture has not
  /// yet been selected. If the tap gesture wins, [onTapUp] will be called,
  /// otherwise [onTapCancel] will be called.
310 311 312 313
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
314
  final GestureTapDownCallback? onTapDown;
315

316 317
  /// A pointer that will trigger a tap with a primary button has stopped
  /// contacting the screen at a particular location.
318 319 320
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
321 322 323 324
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
325
  final GestureTapUpCallback? onTapUp;
326

327
  /// A tap with a primary button has occurred.
328 329 330 331 332 333
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
334
  ///  * [kPrimaryButton], the button this callback responds to.
335 336
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
337
  final GestureTapCallback? onTap;
338

339 340
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
341 342 343
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
344 345 346 347
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
348
  final GestureTapCancelCallback? onTapCancel;
349

350 351 352 353 354 355 356 357 358 359
  /// A tap with a secondary button has occurred.
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onSecondaryTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onSecondaryTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
360
  final GestureTapCallback? onSecondaryTap;
361

362 363 364 365 366 367 368 369 370 371
  /// A pointer that might cause a tap with a secondary button has contacted the
  /// screen at a particular location.
  ///
  /// This is called after a short timeout, even if the winning gesture has not
  /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be
  /// called, otherwise [onSecondaryTapCancel] will be called.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
372
  final GestureTapDownCallback? onSecondaryTapDown;
373 374 375 376 377 378 379 380 381

  /// A pointer that will trigger a tap with a secondary button has stopped
  /// contacting the screen at a particular location.
  ///
  /// This triggers in the case of the tap gesture winning. If the tap gesture
  /// did not win, [onSecondaryTapCancel] is called instead.
  ///
  /// See also:
  ///
382 383
  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
  ///    pass any details about the tap.
384
  ///  * [kSecondaryButton], the button this callback responds to.
385
  final GestureTapUpCallback? onSecondaryTapUp;
386 387 388 389 390 391 392 393 394 395

  /// The pointer that previously triggered [onSecondaryTapDown] will not end up
  /// causing a tap.
  ///
  /// This is called after [onSecondaryTapDown], and instead of
  /// [onSecondaryTapUp], if the tap gesture did not win.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
396
  final GestureTapCancelCallback? onSecondaryTapCancel;
397

398 399 400 401 402 403 404 405 406 407
  /// A pointer that might cause a tap with a tertiary button has contacted the
  /// screen at a particular location.
  ///
  /// This is called after a short timeout, even if the winning gesture has not
  /// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be
  /// called, otherwise [onTertiaryTapCancel] will be called.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
408
  final GestureTapDownCallback? onTertiaryTapDown;
409 410 411 412 413 414 415 416 417 418

  /// A pointer that will trigger a tap with a tertiary button has stopped
  /// contacting the screen at a particular location.
  ///
  /// This triggers in the case of the tap gesture winning. If the tap gesture
  /// did not win, [onTertiaryTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
419
  final GestureTapUpCallback? onTertiaryTapUp;
420 421 422 423 424 425 426 427 428 429

  /// The pointer that previously triggered [onTertiaryTapDown] will not end up
  /// causing a tap.
  ///
  /// This is called after [onTertiaryTapDown], and instead of
  /// [onTertiaryTapUp], if the tap gesture did not win.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
430
  final GestureTapCancelCallback? onTertiaryTapCancel;
431

432 433 434 435 436 437 438 439 440 441 442 443
  /// A pointer that might cause a double tap has contacted the screen at a
  /// particular location.
  ///
  /// Triggered immediately after the down event of the second tap.
  ///
  /// If the user completes the double tap and the gesture wins, [onDoubleTap]
  /// will be called after this callback. Otherwise, [onDoubleTapCancel] will
  /// be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
444
  final GestureTapDownCallback? onDoubleTapDown;
445

446 447 448 449 450 451
  /// The user has tapped the screen with a primary button at the same location
  /// twice in quick succession.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
452
  final GestureTapCallback? onDoubleTap;
453

454 455 456 457 458 459
  /// The pointer that previously triggered [onDoubleTapDown] will not end up
  /// causing a double tap.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
460
  final GestureTapCancelCallback? onDoubleTapCancel;
461

462
  /// Called when a long press gesture with a primary button has been recognized.
463 464 465 466 467 468
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
469 470
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressStart], which has the same timing but has gesture details.
471
  final GestureLongPressCallback? onLongPress;
472

473
  /// Called when a long press gesture with a primary button has been recognized.
474 475 476 477 478 479
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
480 481
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPress], which has the same timing but without the gesture details.
482
  final GestureLongPressStartCallback? onLongPressStart;
483

484 485 486 487 488
  /// A pointer has been drag-moved after a long press with a primary button.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
489
  final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
490

491 492
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
493 494 495
  ///
  /// See also:
  ///
496 497
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressEnd], which has the same timing but has gesture details.
498
  final GestureLongPressUpCallback? onLongPressUp;
499

500 501
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
502 503 504
  ///
  /// See also:
  ///
505 506 507
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressUp], which has the same timing but without the gesture
  ///    details.
508
  final GestureLongPressEndCallback? onLongPressEnd;
509

510 511 512 513 514 515 516 517 518 519 520
  /// Called when a long press gesture with a secondary button has been
  /// recognized.
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onSecondaryLongPressStart], which has the same timing but has gesture
  ///    details.
521
  final GestureLongPressCallback? onSecondaryLongPress;
522 523 524 525 526 527 528 529 530 531 532 533

  /// Called when a long press gesture with a secondary button has been
  /// recognized.
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onSecondaryLongPress], which has the same timing but without the
  ///    gesture details.
534
  final GestureLongPressStartCallback? onSecondaryLongPressStart;
535 536 537 538 539 540

  /// A pointer has been drag-moved after a long press with a secondary button.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
541
  final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
542 543 544 545 546 547 548 549 550

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onSecondaryLongPressEnd], which has the same timing but has gesture
  ///    details.
551
  final GestureLongPressUpCallback? onSecondaryLongPressUp;
552 553 554 555 556 557 558 559 560

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onSecondaryLongPressUp], which has the same timing but without the
  ///    gesture details.
561
  final GestureLongPressEndCallback? onSecondaryLongPressEnd;
562

563 564 565 566 567 568
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move vertically.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
569
  final GestureDragDownCallback? onVerticalDragDown;
570

571 572 573 574 575 576
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move vertically.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
577
  final GestureDragStartCallback? onVerticalDragStart;
578

579 580 581 582 583 584
  /// A pointer that is in contact with the screen with a primary button and
  /// moving vertically has moved in the vertical direction.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
585
  final GestureDragUpdateCallback? onVerticalDragUpdate;
586

587 588 589 590 591 592 593
  /// A pointer that was previously in contact with the screen with a primary
  /// button and moving vertically is no longer in contact with the screen and
  /// was moving at a specific velocity when it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
594
  final GestureDragEndCallback? onVerticalDragEnd;
595

596 597
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
598 599 600 601
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
602
  final GestureDragCancelCallback? onVerticalDragCancel;
603

604 605 606 607 608 609
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move horizontally.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
610
  final GestureDragDownCallback? onHorizontalDragDown;
611

612 613 614 615 616 617
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move horizontally.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
618
  final GestureDragStartCallback? onHorizontalDragStart;
619

620 621 622 623 624 625
  /// A pointer that is in contact with the screen with a primary button and
  /// moving horizontally has moved in the horizontal direction.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
626
  final GestureDragUpdateCallback? onHorizontalDragUpdate;
627

628 629 630 631 632 633 634
  /// A pointer that was previously in contact with the screen with a primary
  /// button and moving horizontally is no longer in contact with the screen and
  /// was moving at a specific velocity when it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
635
  final GestureDragEndCallback? onHorizontalDragEnd;
636

637 638
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
639 640 641 642
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
643
  final GestureDragCancelCallback? onHorizontalDragCancel;
644

645 646 647 648 649 650
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
651
  final GestureDragDownCallback? onPanDown;
652

653 654 655 656 657 658
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
659
  final GestureDragStartCallback? onPanStart;
660

661 662 663 664 665 666
  /// A pointer that is in contact with the screen with a primary button and
  /// moving has moved again.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
667
  final GestureDragUpdateCallback? onPanUpdate;
668

669 670 671 672 673 674 675
  /// A pointer that was previously in contact with the screen with a primary
  /// button and moving is no longer in contact with the screen and was moving
  /// at a specific velocity when it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
676
  final GestureDragEndCallback? onPanEnd;
677 678

  /// The pointer that previously triggered [onPanDown] did not complete.
679 680 681 682
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
683
  final GestureDragCancelCallback? onPanCancel;
684

685 686
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
687
  final GestureScaleStartCallback? onScaleStart;
688 689 690

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
691
  final GestureScaleUpdateCallback? onScaleUpdate;
692 693

  /// The pointers are no longer in contact with the screen.
694
  final GestureScaleEndCallback? onScaleEnd;
695

696 697 698 699 700 701
  /// The pointer is in contact with the screen and has pressed with sufficient
  /// force to initiate a force press. The amount of force is at least
  /// [ForcePressGestureRecognizer.startPressure].
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
702
  final GestureForcePressStartCallback? onForcePressStart;
703 704 705 706 707 708 709

  /// The pointer is in contact with the screen and has pressed with the maximum
  /// force. The amount of force is at least
  /// [ForcePressGestureRecognizer.peakPressure].
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
710
  final GestureForcePressPeakCallback? onForcePressPeak;
711 712 713 714 715 716 717 718

  /// A pointer is in contact with the screen, has previously passed the
  /// [ForcePressGestureRecognizer.startPressure] and is either moving on the
  /// plane of the screen, pressing the screen with varying forces or both
  /// simultaneously.
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
719
  final GestureForcePressUpdateCallback? onForcePressUpdate;
720 721 722 723 724

  /// The pointer is no longer in contact with the screen.
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
725
  final GestureForcePressEndCallback? onForcePressEnd;
726

727
  /// How this gesture detector should behave during hit testing.
728 729 730
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
731
  final HitTestBehavior? behavior;
732

Hixie's avatar
Hixie committed
733 734 735 736 737 738 739
  /// Whether to exclude these gestures from the semantics tree. For
  /// example, the long-press gesture for showing a tooltip is
  /// excluded because the tooltip itself is included in the semantics
  /// tree directly and so having a gesture to show it would result in
  /// duplication of information.
  final bool excludeFromSemantics;

740 741 742 743 744 745 746 747 748 749
  /// Determines the way that drag start behavior is handled.
  ///
  /// If set to [DragStartBehavior.start], gesture drag behavior will
  /// begin upon the detection of a drag gesture. If set to
  /// [DragStartBehavior.down] it will begin when a down event is first detected.
  ///
  /// In general, setting this to [DragStartBehavior.start] will make drag
  /// animation smoother and setting it to [DragStartBehavior.down] will make
  /// drag behavior feel slightly more reactive.
  ///
750
  /// By default, the drag start behavior is [DragStartBehavior.start].
751
  ///
752 753 754
  /// Only the [DragGestureRecognizer.onStart] callbacks for the
  /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and
  /// [PanGestureRecognizer] are affected by this setting.
755 756 757 758 759 760
  ///
  /// See also:
  ///
  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
  final DragStartBehavior dragStartBehavior;

761
  @override
762
  Widget build(BuildContext context) {
763
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
764

765 766 767 768 769 770 771
    if (onTapDown != null ||
        onTapUp != null ||
        onTap != null ||
        onTapCancel != null ||
        onSecondaryTap != null ||
        onSecondaryTapDown != null ||
        onSecondaryTapUp != null ||
772 773 774 775
        onSecondaryTapCancel != null||
        onTertiaryTapDown != null ||
        onTertiaryTapUp != null ||
        onTertiaryTapCancel != null
776
    ) {
777 778
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
779 780 781 782 783
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
784
            ..onTapCancel = onTapCancel
785
            ..onSecondaryTap = onSecondaryTap
786 787
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
788 789 790 791
            ..onSecondaryTapCancel = onSecondaryTapCancel
            ..onTertiaryTapDown = onTertiaryTapDown
            ..onTertiaryTapUp = onTertiaryTapUp
            ..onTertiaryTapCancel = onTertiaryTapCancel;
792 793
        },
      );
794
    }
795

796
    if (onDoubleTap != null) {
797 798
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
799
        (DoubleTapGestureRecognizer instance) {
800 801 802 803
          instance
            ..onDoubleTapDown = onDoubleTapDown
            ..onDoubleTap = onDoubleTap
            ..onDoubleTapCancel = onDoubleTapCancel;
804 805
        },
      );
806
    }
807

808 809 810 811
    if (onLongPress != null ||
        onLongPressUp != null ||
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
812 813
        onLongPressEnd != null ||
        onSecondaryLongPress != null ||
814 815 816 817 818 819 820 821
        onSecondaryLongPressUp != null ||
        onSecondaryLongPressStart != null ||
        onSecondaryLongPressMoveUpdate != null ||
        onSecondaryLongPressEnd != null) {
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
822 823 824 825 826
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressEnd = onLongPressEnd
            ..onLongPressUp = onLongPressUp
827 828 829
            ..onSecondaryLongPress = onSecondaryLongPress
            ..onSecondaryLongPressStart = onSecondaryLongPressStart
            ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
830
            ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
831 832 833 834 835
            ..onSecondaryLongPressUp = onSecondaryLongPressUp;
        },
      );
    }

836 837 838 839 840
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
841 842
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
843 844 845 846 847 848
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
849 850
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior = dragStartBehavior;
851 852
        },
      );
853
    }
854

855 856 857 858 859
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
860 861
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
862 863 864 865 866 867
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
868 869
            ..onCancel = onHorizontalDragCancel
            ..dragStartBehavior = dragStartBehavior;
870 871
        },
      );
872
    }
873

874 875 876 877 878
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
879 880
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
881 882 883 884 885 886
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
887 888
            ..onCancel = onPanCancel
            ..dragStartBehavior = dragStartBehavior;
889 890
        },
      );
891
    }
892

893
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
894 895
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
896 897 898 899
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
900 901
            ..onEnd = onScaleEnd
            ..dragStartBehavior = dragStartBehavior;
902 903
        },
      );
904
    }
905

906 907 908 909 910
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
911 912 913 914 915 916 917
        () => ForcePressGestureRecognizer(debugOwner: this),
        (ForcePressGestureRecognizer instance) {
          instance
            ..onStart = onForcePressStart
            ..onPeak = onForcePressPeak
            ..onUpdate = onForcePressUpdate
            ..onEnd = onForcePressEnd;
918 919 920 921
        },
      );
    }

922
    return RawGestureDetector(
923 924 925
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
926
      child: child,
927
    );
928
  }
929

930 931 932 933 934
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
935
}
936

937 938 939 940
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
941
/// [RawGestureDetector] is useful primarily when developing your
942
/// own gesture recognizers.
943 944 945 946
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
947
/// {@tool snippet}
948 949 950 951 952 953
///
/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that
/// the code is being used inside a [State] object with a `_last` field that is
/// then displayed as the child of the gesture detector.
///
/// ```dart
954
/// RawGestureDetector(
955
///   gestures: <Type, GestureRecognizerFactory>{
956 957
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
958 959
///       (TapGestureRecognizer instance) {
///         instance
960 961 962 963
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
964 965 966
///       },
///     ),
///   },
967
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
968 969
/// )
/// ```
970
/// {@end-tool}
971 972 973 974
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
975
///  * [Listener], a widget that reports raw pointer events.
976
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
977
class RawGestureDetector extends StatefulWidget {
978 979
  /// Creates a widget that detects gestures.
  ///
980 981 982
  /// Gesture detectors can contribute semantic information to the tree that is
  /// used by assistive technology. The behavior can be configured by
  /// [semantics], or disabled with [excludeFromSemantics].
983
  const RawGestureDetector({
984
    Key? key,
985
    this.child,
986
    this.gestures = const <Type, GestureRecognizerFactory>{},
987
    this.behavior,
988
    this.excludeFromSemantics = false,
989
    this.semantics,
990 991 992
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
993

994
  /// The widget below this widget in the tree.
995
  ///
996
  /// {@macro flutter.widgets.ProxyWidget.child}
997
  final Widget? child;
998

999
  /// The gestures that this widget will attempt to recognize.
1000 1001 1002 1003 1004 1005
  ///
  /// This should be a map from [GestureRecognizer] subclasses to
  /// [GestureRecognizerFactory] subclasses specialized with the same type.
  ///
  /// This value can be late-bound at layout time using
  /// [RawGestureDetectorState.replaceGestureRecognizers].
1006 1007 1008
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
1009 1010 1011
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
1012
  final HitTestBehavior? behavior;
1013 1014 1015 1016 1017 1018 1019 1020

  /// Whether to exclude these gestures from the semantics tree. For
  /// example, the long-press gesture for showing a tooltip is
  /// excluded because the tooltip itself is included in the semantics
  /// tree directly and so having a gesture to show it would result in
  /// duplication of information.
  final bool excludeFromSemantics;

1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
  /// Describes the semantics notations that should be added to the underlying
  /// render object [RenderSemanticsGestureHandler].
  ///
  /// It has no effect if [excludeFromSemantics] is true.
  ///
  /// When [semantics] is null, [RawGestureDetector] will fall back to a
  /// default delegate which checks if the detector owns certain gesture
  /// recognizers and calls their callbacks if they exist:
  ///
  ///  * During a semantic tap, it calls [TapGestureRecognizer]'s
  ///    `onTapDown`, `onTapUp`, and `onTap`.
  ///  * During a semantic long press, it calls [LongPressGestureRecognizer]'s
  ///    `onLongPressStart`, `onLongPress`, `onLongPressEnd` and `onLongPressUp`.
  ///  * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s
  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
  ///  * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s
  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
  ///
1041
  /// {@tool snippet}
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
  /// This custom gesture detector listens to force presses, while also allows
  /// the same callback to be triggered by semantic long presses.
  ///
  /// ```dart
  /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget {
  ///   const ForcePressGestureDetectorWithSemantics({
  ///     this.child,
  ///     this.onForcePress,
  ///   });
  ///
  ///   final Widget child;
  ///   final VoidCallback onForcePress;
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     return RawGestureDetector(
  ///       gestures: <Type, GestureRecognizerFactory>{
  ///         ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
  ///           () => ForcePressGestureRecognizer(debugOwner: this),
  ///           (ForcePressGestureRecognizer instance) {
  ///             instance.onStart = (_) => onForcePress();
  ///           }
  ///         ),
  ///       },
  ///       behavior: HitTestBehavior.opaque,
  ///       semantics: _LongPressSemanticsDelegate(onForcePress),
  ///       child: child,
  ///     );
  ///   }
  /// }
  ///
  /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate {
  ///   _LongPressSemanticsDelegate(this.onLongPress);
  ///
  ///   VoidCallback onLongPress;
  ///
  ///   @override
  ///   void assignSemantics(RenderSemanticsGestureHandler renderObject) {
  ///     renderObject.onLongPress = onLongPress;
  ///   }
  /// }
  /// ```
  /// {@end-tool}
1085
  final SemanticsGestureDelegate? semantics;
1086

1087
  @override
1088
  RawGestureDetectorState createState() => RawGestureDetectorState();
1089 1090
}

1091
/// State for a [RawGestureDetector].
1092
class RawGestureDetectorState extends State<RawGestureDetector> {
1093 1094
  Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
  SemanticsGestureDelegate? _semantics;
1095

1096
  @override
1097 1098
  void initState() {
    super.initState();
1099
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
1100
    _syncAll(widget.gestures);
1101 1102
  }

1103
  @override
1104
  void didUpdateWidget(RawGestureDetector oldWidget) {
1105
    super.didUpdateWidget(oldWidget);
1106 1107 1108
    if (!(oldWidget.semantics == null && widget.semantics == null)) {
      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    }
1109
    _syncAll(widget.gestures);
1110 1111
  }

1112
  /// This method can be called after the build phase, during the
1113
  /// layout of the nearest descendant [RenderObjectWidget] of the
1114 1115 1116 1117 1118 1119 1120
  /// gesture detector, to update the list of active gesture
  /// recognizers.
  ///
  /// The typical use case is [Scrollable]s, which put their viewport
  /// in their gesture detector, and then need to know the dimensions
  /// of the viewport and the viewport's child to determine whether
  /// the gesture detector should be enabled.
1121 1122 1123 1124
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
1125 1126
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
1127
      if (!context.findRenderObject()!.owner!.debugDoingLayout) {
1128 1129 1130 1131 1132 1133 1134
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.'),
          ErrorDescription('The replaceGestureRecognizers() method can only be called during the layout phase.'),
          ErrorHint(
            'To set the gesture recognizers at other times, trigger a new build using setState() '
            'and provide the new gesture recognizers as constructor arguments to the corresponding '
            'RawGestureDetector or GestureDetector object.'
1135
          ),
1136
        ]);
1137
      }
1138
      return true;
1139
    }());
1140
    _syncAll(gestures);
1141
    if (!widget.excludeFromSemantics) {
1142
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler;
1143
      _updateSemanticsForRenderObject(semanticsGestureHandler);
1144 1145 1146
    }
  }

1147 1148
  /// This method can be called to filter the list of available semantic actions,
  /// after the render object was created.
1149 1150 1151
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
1152 1153 1154 1155 1156
  ///
  /// This is used by [Scrollable] to configure system accessibility tools so
  /// that they know in which direction a particular list can be scrolled.
  ///
  /// If this is never called, then the actions are not filtered. If the list of
1157
  /// actions to filter changes, it must be called again.
1158
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
1159 1160 1161
    if (widget.excludeFromSemantics)
      return;

1162
    final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?;
1163
    assert(() {
1164
      if (semanticsGestureHandler == null) {
1165
        throw FlutterError(
1166
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
1167
          'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.'
1168 1169 1170
        );
      }
      return true;
1171
    }());
1172

1173
    semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
1174 1175
  }

1176
  @override
1177
  void dispose() {
1178
    for (final GestureRecognizer recognizer in _recognizers!.values)
1179 1180 1181
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
1182 1183
  }

1184 1185
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
1186
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
1187
    _recognizers = <Type, GestureRecognizer>{};
1188
    for (final Type type in gestures.keys) {
1189
      assert(gestures[type] != null);
1190 1191 1192 1193 1194
      assert(gestures[type]!._debugAssertTypeMatches(type));
      assert(!_recognizers!.containsKey(type));
      _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
      assert(_recognizers![type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
      gestures[type]!.initializer(_recognizers![type]!);
1195
    }
1196
    for (final Type type in oldRecognizers.keys) {
1197 1198
      if (!_recognizers!.containsKey(type))
        oldRecognizers[type]!.dispose();
1199
    }
1200 1201
  }

Ian Hickson's avatar
Ian Hickson committed
1202
  void _handlePointerDown(PointerDownEvent event) {
1203
    assert(_recognizers != null);
1204
    for (final GestureRecognizer recognizer in _recognizers!.values)
1205
      recognizer.addPointer(event);
1206 1207
  }

1208
  HitTestBehavior get _defaultBehavior {
1209
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
1210 1211
  }

1212 1213 1214
  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
    assert(!widget.excludeFromSemantics);
    assert(_semantics != null);
1215
    _semantics!.assignSemantics(renderObject);
1216 1217
  }

1218
  @override
1219
  Widget build(BuildContext context) {
1220
    Widget result = Listener(
1221 1222
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
1223
      child: widget.child,
1224 1225
    );
    if (!widget.excludeFromSemantics)
1226 1227 1228 1229
      result = _GestureSemantics(
        child: result,
        assignSemantics: _updateSemanticsForRenderObject,
      );
1230 1231
    return result;
  }
Hixie's avatar
Hixie committed
1232

1233
  @override
1234 1235
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1236
    if (_recognizers == null) {
1237
      properties.add(DiagnosticsNode.message('DISPOSED'));
1238
    } else {
1239
      final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
1240
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
1241
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers!.values, level: DiagnosticLevel.fine));
1242 1243 1244 1245
      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
      if (!widget.excludeFromSemantics) {
        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
      }
1246
    }
1247
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1248 1249 1250
  }
}

1251 1252
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);

1253 1254
class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
1255 1256 1257
    Key? key,
    Widget? child,
    required this.assignSemantics,
1258 1259
  }) : assert(assignSemantics != null),
       super(key: key, child: child);
1260

1261
  final _AssignSemantics assignSemantics;
1262 1263 1264

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1265 1266 1267
    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler();
    assignSemantics(renderObject);
    return renderObject;
1268 1269
  }

1270 1271 1272
  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
    assignSemantics(renderObject);
Hixie's avatar
Hixie committed
1273
  }
1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287
}

/// A base class that describes what semantics notations a [RawGestureDetector]
/// should add to the render object [RenderSemanticsGestureHandler].
///
/// It is used to allow custom [GestureDetector]s to add semantics notations.
abstract class SemanticsGestureDelegate {
  /// Create a delegate of gesture semantics.
  const SemanticsGestureDelegate();

  /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render
  /// object of the gesture detector.
  ///
  /// This method is called when the widget is created, updated, or during
1288
  /// [RawGestureDetectorState.replaceGestureRecognizers].
1289
  void assignSemantics(RenderSemanticsGestureHandler renderObject);
1290 1291

  @override
1292
  String toString() => '${objectRuntimeType(this, 'SemanticsGestureDelegate')}()';
1293 1294 1295 1296 1297 1298 1299 1300
}

// The default semantics delegate of [RawGestureDetector]. Its behavior is
// described in [RawGestureDetector.semantics].
//
// For readers who come here to learn how to write custom semantics delegates:
// this is not a proper sample code. It has access to the detector state as well
// as its private properties, which are inaccessible normally. It is designed
1301
// this way in order to work independently in a [RawGestureRecognizer] to
1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
// preserve existing behavior.
//
// Instead, a normal delegate will store callbacks as properties, and use them
// in `assignSemantics`.
class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate {
  _DefaultSemanticsGestureDelegate(this.detectorState);

  final RawGestureDetectorState detectorState;

  @override
  void assignSemantics(RenderSemanticsGestureHandler renderObject) {
    assert(!detectorState.widget.excludeFromSemantics);
1314
    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!;
1315 1316 1317 1318 1319
    renderObject
      ..onTap = _getTapHandler(recognizers)
      ..onLongPress = _getLongPressHandler(recognizers)
      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1320
  }
1321

1322 1323
  GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
    final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?;
1324 1325 1326 1327 1328 1329 1330
    if (tap == null)
      return null;
    assert(tap is TapGestureRecognizer);

    return () {
      assert(tap != null);
      if (tap.onTapDown != null)
1331
        tap.onTapDown!(TapDownDetails());
1332
      if (tap.onTapUp != null)
1333
        tap.onTapUp!(TapUpDetails(kind: PointerDeviceKind.unknown));
1334
      if (tap.onTap != null)
1335
        tap.onTap!();
1336
    };
1337
  }
1338

1339 1340
  GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
    final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?;
1341 1342 1343 1344 1345 1346
    if (longPress == null)
      return null;

    return () {
      assert(longPress is LongPressGestureRecognizer);
      if (longPress.onLongPressStart != null)
1347
        longPress.onLongPressStart!(const LongPressStartDetails());
1348
      if (longPress.onLongPress != null)
1349
        longPress.onLongPress!();
1350
      if (longPress.onLongPressEnd != null)
1351
        longPress.onLongPressEnd!(const LongPressEndDetails());
1352
      if (longPress.onLongPressUp != null)
1353
        longPress.onLongPressUp!();
1354
    };
1355
  }
1356

1357 1358 1359
  GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1360

1361
    final GestureDragUpdateCallback? horizontalHandler = horizontal == null ?
1362 1363 1364 1365
      null :
      (DragUpdateDetails details) {
        assert(horizontal is HorizontalDragGestureRecognizer);
        if (horizontal.onDown != null)
1366
          horizontal.onDown!(DragDownDetails());
1367
        if (horizontal.onStart != null)
1368
          horizontal.onStart!(DragStartDetails());
1369
        if (horizontal.onUpdate != null)
1370
          horizontal.onUpdate!(details);
1371
        if (horizontal.onEnd != null)
1372
          horizontal.onEnd!(DragEndDetails(primaryVelocity: 0.0));
1373 1374
      };

1375
    final GestureDragUpdateCallback? panHandler = pan == null ?
1376 1377 1378 1379
      null :
      (DragUpdateDetails details) {
        assert(pan is PanGestureRecognizer);
        if (pan.onDown != null)
1380
          pan.onDown!(DragDownDetails());
1381
        if (pan.onStart != null)
1382
          pan.onStart!(DragStartDetails());
1383
        if (pan.onUpdate != null)
1384
          pan.onUpdate!(details);
1385
        if (pan.onEnd != null)
1386
          pan.onEnd!(DragEndDetails());
1387 1388 1389 1390 1391 1392 1393 1394 1395 1396
      };

    if (horizontalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (horizontalHandler != null)
        horizontalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1397
  }
1398

1399 1400 1401
  GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1402

1403
    final GestureDragUpdateCallback? verticalHandler = vertical == null ?
1404 1405 1406 1407
      null :
      (DragUpdateDetails details) {
        assert(vertical is VerticalDragGestureRecognizer);
        if (vertical.onDown != null)
1408
          vertical.onDown!(DragDownDetails());
1409
        if (vertical.onStart != null)
1410
          vertical.onStart!(DragStartDetails());
1411
        if (vertical.onUpdate != null)
1412
          vertical.onUpdate!(details);
1413
        if (vertical.onEnd != null)
1414
          vertical.onEnd!(DragEndDetails(primaryVelocity: 0.0));
1415 1416
      };

1417
    final GestureDragUpdateCallback? panHandler = pan == null ?
1418 1419 1420 1421
      null :
      (DragUpdateDetails details) {
        assert(pan is PanGestureRecognizer);
        if (pan.onDown != null)
1422
          pan.onDown!(DragDownDetails());
1423
        if (pan.onStart != null)
1424
          pan.onStart!(DragStartDetails());
1425
        if (pan.onUpdate != null)
1426
          pan.onUpdate!(details);
1427
        if (pan.onEnd != null)
1428
          pan.onEnd!(DragEndDetails());
1429 1430 1431 1432 1433 1434 1435 1436 1437 1438
      };

    if (verticalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (verticalHandler != null)
        verticalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1439
  }
Hixie's avatar
Hixie committed
1440
}