gesture_detector.dart 64.2 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
// late bool _lights;
52
// void setState(VoidCallback fn) { }
53 54
// late String _last;
// late Color _color;
55

56
/// Factory for creating gesture recognizers.
57
///
58
/// `T` is the type of gesture recognizer this class manages.
59 60
///
/// Used by [RawGestureDetector.gestures].
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
@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].
83
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
84 85

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

/// 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.
95 96 97
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
    : assert(_constructor != null),
      assert(_initializer != null);
98 99 100 101 102 103 104 105 106 107 108

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

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

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

110 111
/// A widget that detects gestures.
///
112
/// Attempts to recognize gestures that correspond to its non-null callbacks.
113
///
114 115 116
/// 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.
///
117 118 119
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
120 121 122 123
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
124
/// See <http://flutter.dev/gestures/> for additional information.
125 126 127 128
///
/// 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.
129
///
130
/// {@tool dartpad --template=stateful_widget_material}
131
///
132 133 134
/// This example contains a black light bulb wrapped in a [GestureDetector]. It
/// turns the light bulb yellow when the "TURN LIGHT ON" button is tapped by
/// setting the `_lights` field, and off again when "TURN LIGHT OFF" is tapped.
135 136
///
/// ```dart
137 138 139 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 167 168 169
/// bool _lightIsOn = false;
///
/// @override
/// Widget build(BuildContext context) {
///   return Scaffold(
///     body: Container(
///       alignment: FractionalOffset.center,
///       child: Column(
///         mainAxisAlignment: MainAxisAlignment.center,
///         children: <Widget>[
///           Padding(
///             padding: const EdgeInsets.all(8.0),
///             child: Icon(
///               Icons.lightbulb_outline,
///               color: _lightIsOn ? Colors.yellow.shade600 : Colors.black,
///               size: 60,
///             ),
///           ),
///           GestureDetector(
///             onTap: () {
///               setState(() {
///                 // Toggle light when tapped.
///                 _lightIsOn = !_lightIsOn;
///               });
///             },
///             child: Container(
///               color: Colors.yellow.shade600,
///               padding: const EdgeInsets.all(8),
///               // Change button text when light changes state.
///               child: Text(_lightIsOn ? 'TURN LIGHT OFF' : 'TURN LIGHT ON'),
///             ),
///           ),
///         ],
170
///       ),
171 172 173
///     ),
///   );
/// }
174
/// ```
175
/// {@end-tool}
176
///
177 178 179 180
/// {@tool dartpad --template=stateful_widget_material}
///
/// This example uses a [Container] that wraps a [GestureDetector] widget which
/// detects a tap.
181
///
182 183 184 185
/// 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. When
/// tapped again, it goes back to white.
186 187
///
/// ```dart
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
/// Color _color = Colors.white;
///
/// @override
/// Widget build(BuildContext context) {
///   return Container(
///     color: _color,
///     height: 200.0,
///     width: 200.0,
///     child: GestureDetector(
///       onTap: () {
///         setState(() {
///           _color == Colors.yellow ? _color = Colors.white : _color = Colors.yellow;
///         });
///       },
///     ),
///   );
/// }
205 206 207
/// ```
/// {@end-tool}
///
208 209 210 211
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
212 213 214 215 216 217
///
/// 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.
218
///  * [RawGestureDetector], a widget that is used to detect custom gestures.
219
class GestureDetector extends StatelessWidget {
220 221 222 223 224 225 226 227 228 229 230
  /// 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.
231
  GestureDetector({
232
    Key? key,
233
    this.child,
234
    this.onTapDown,
235 236
    this.onTapUp,
    this.onTap,
237
    this.onTapCancel,
238
    this.onSecondaryTap,
239 240 241
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,
242 243 244
    this.onTertiaryTapDown,
    this.onTertiaryTapUp,
    this.onTertiaryTapCancel,
245
    this.onDoubleTapDown,
246
    this.onDoubleTap,
247
    this.onDoubleTapCancel,
248 249
    this.onLongPressDown,
    this.onLongPressCancel,
250
    this.onLongPress,
251 252
    this.onLongPressStart,
    this.onLongPressMoveUpdate,
253
    this.onLongPressUp,
254
    this.onLongPressEnd,
255 256
    this.onSecondaryLongPressDown,
    this.onSecondaryLongPressCancel,
257 258 259 260 261
    this.onSecondaryLongPress,
    this.onSecondaryLongPressStart,
    this.onSecondaryLongPressMoveUpdate,
    this.onSecondaryLongPressUp,
    this.onSecondaryLongPressEnd,
262 263 264 265 266 267 268
    this.onTertiaryLongPressDown,
    this.onTertiaryLongPressCancel,
    this.onTertiaryLongPress,
    this.onTertiaryLongPressStart,
    this.onTertiaryLongPressMoveUpdate,
    this.onTertiaryLongPressUp,
    this.onTertiaryLongPressEnd,
269
    this.onVerticalDragDown,
270 271 272
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
273 274
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
275 276 277
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
278
    this.onHorizontalDragCancel,
279 280 281 282
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
283
    this.onPanDown,
284 285
    this.onPanStart,
    this.onPanUpdate,
286
    this.onPanEnd,
287
    this.onPanCancel,
288 289
    this.onScaleStart,
    this.onScaleUpdate,
290
    this.onScaleEnd,
Hixie's avatar
Hixie committed
291
    this.behavior,
292
    this.excludeFromSemantics = false,
293
    this.dragStartBehavior = DragStartBehavior.start,
294
  }) : assert(excludeFromSemantics != null),
295
       assert(dragStartBehavior != null),
296 297 298 299 300 301 302
       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) {
303 304 305
             throw FlutterError.fromParts(<DiagnosticsNode>[
               ErrorSummary('Incorrect GestureDetector arguments.'),
               ErrorDescription(
306
                 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',
307
               ),
308
               ErrorHint('Just use the scale gesture recognizer.'),
309
             ]);
310 311 312
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
313 314 315
             throw FlutterError(
               'Incorrect GestureDetector arguments.\n'
               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
316
               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.',
317
             );
318 319 320
           }
         }
         return true;
321
       }()),
322
       super(key: key);
323

324
  /// The widget below this widget in the tree.
325
  ///
326
  /// {@macro flutter.widgets.ProxyWidget.child}
327
  final Widget? child;
328

329 330
  /// A pointer that might cause a tap with a primary button has contacted the
  /// screen at a particular location.
331 332 333 334
  ///
  /// 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.
335 336 337 338
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
339
  final GestureTapDownCallback? onTapDown;
340

341 342
  /// A pointer that will trigger a tap with a primary button has stopped
  /// contacting the screen at a particular location.
343 344 345
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
346 347 348 349
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
350
  final GestureTapUpCallback? onTapUp;
351

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

364 365
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
366 367 368
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
369 370 371 372
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
373
  final GestureTapCancelCallback? onTapCancel;
374

375 376 377 378 379 380 381 382 383 384
  /// 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.
385
  final GestureTapCallback? onSecondaryTap;
386

387 388 389 390 391 392 393 394 395 396
  /// 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.
397
  final GestureTapDownCallback? onSecondaryTapDown;
398 399 400 401 402 403 404 405 406

  /// 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:
  ///
407 408
  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
  ///    pass any details about the tap.
409
  ///  * [kSecondaryButton], the button this callback responds to.
410
  final GestureTapUpCallback? onSecondaryTapUp;
411 412 413 414 415 416 417 418 419 420

  /// 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.
421
  final GestureTapCancelCallback? onSecondaryTapCancel;
422

423 424 425 426 427 428 429 430 431 432
  /// 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.
433
  final GestureTapDownCallback? onTertiaryTapDown;
434 435 436 437 438 439 440 441 442 443

  /// 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.
444
  final GestureTapUpCallback? onTertiaryTapUp;
445 446 447 448 449 450 451 452 453 454

  /// 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.
455
  final GestureTapCancelCallback? onTertiaryTapCancel;
456

457 458 459 460 461 462 463 464 465 466 467 468
  /// 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.
469
  final GestureTapDownCallback? onDoubleTapDown;
470

471 472 473 474 475 476
  /// 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.
477
  final GestureTapCallback? onDoubleTap;
478

479 480 481 482 483 484
  /// The pointer that previously triggered [onDoubleTapDown] will not end up
  /// causing a double tap.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
485
  final GestureTapCancelCallback? onDoubleTapCancel;
486

487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
  /// The pointer has contacted the screen with a primary button, which might
  /// be the start of a long-press.
  ///
  /// This triggers after the pointer down event.
  ///
  /// If the user completes the long-press, and this gesture wins,
  /// [onLongPressStart] will be called after this callback. Otherwise,
  /// [onLongPressCancel] will be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryLongPressDown], a similar callback but for a secondary button.
  ///  * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
  ///  * [LongPressGestureRecognizer.onLongPressDown], which exposes this
  ///    callback at the gesture layer.
  final GestureLongPressDownCallback? onLongPressDown;

  /// A pointer that previously triggered [onLongPressDown] will not end up
  /// causing a long-press.
  ///
  /// This triggers once the gesture loses if [onLongPressDown] has previously
  /// been triggered.
  ///
  /// If the user completed the long-press, and the gesture won, then
  /// [onLongPressStart] and [onLongPress] are called instead.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onLongPressCancel], which exposes this
  ///    callback at the gesture layer.
  final GestureLongPressCancelCallback? onLongPressCancel;

521
  /// Called when a long press gesture with a primary button has been recognized.
522 523 524 525
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
526 527 528 529 530
  /// This is equivalent to (and is called immediately after) [onLongPressStart].
  /// The only difference between the two is that this callback does not
  /// contain details of the position at which the pointer initially contacted
  /// the screen.
  ///
531 532
  /// See also:
  ///
533
  ///  * [kPrimaryButton], the button this callback responds to.
534 535
  ///  * [LongPressGestureRecognizer.onLongPress], which exposes this
  ///    callback at the gesture layer.
536
  final GestureLongPressCallback? onLongPress;
537

538
  /// Called when a long press gesture with a primary button has been recognized.
539 540 541 542
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
543 544 545 546 547
  /// This is equivalent to (and is called immediately before) [onLongPress].
  /// The only difference between the two is that this callback contains
  /// details of the position at which the pointer initially contacted the
  /// screen, whereas [onLongPress] does not.
  ///
548 549
  /// See also:
  ///
550
  ///  * [kPrimaryButton], the button this callback responds to.
551 552
  ///  * [LongPressGestureRecognizer.onLongPressStart], which exposes this
  ///    callback at the gesture layer.
553
  final GestureLongPressStartCallback? onLongPressStart;
554

555
  /// A pointer has been drag-moved after a long-press with a primary button.
556 557 558 559
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
560 561
  ///  * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this
  ///    callback at the gesture layer.
562
  final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
563

564 565
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
566
  ///
567 568 569 570 571
  /// This is equivalent to (and is called immediately after) [onLongPressEnd].
  /// The only difference between the two is that this callback does not
  /// contain details of the state of the pointer when it stopped contacting
  /// the screen.
  ///
572 573
  /// See also:
  ///
574
  ///  * [kPrimaryButton], the button this callback responds to.
575 576
  ///  * [LongPressGestureRecognizer.onLongPressUp], which exposes this
  ///    callback at the gesture layer.
577
  final GestureLongPressUpCallback? onLongPressUp;
578

579 580
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
581
  ///
582 583 584 585 586
  /// This is equivalent to (and is called immediately before) [onLongPressUp].
  /// The only difference between the two is that this callback contains
  /// details of the state of the pointer when it stopped contacting the
  /// screen, whereas [onLongPressUp] does not.
  ///
587 588
  /// See also:
  ///
589
  ///  * [kPrimaryButton], the button this callback responds to.
590 591
  ///  * [LongPressGestureRecognizer.onLongPressEnd], which exposes this
  ///    callback at the gesture layer.
592
  final GestureLongPressEndCallback? onLongPressEnd;
593

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
  /// The pointer has contacted the screen with a secondary button, which might
  /// be the start of a long-press.
  ///
  /// This triggers after the pointer down event.
  ///
  /// If the user completes the long-press, and this gesture wins,
  /// [onSecondaryLongPressStart] will be called after this callback. Otherwise,
  /// [onSecondaryLongPressCancel] will be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onLongPressDown], a similar callback but for a secondary button.
  ///  * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressDownCallback? onSecondaryLongPressDown;

  /// A pointer that previously triggered [onSecondaryLongPressDown] will not
  /// end up causing a long-press.
  ///
  /// This triggers once the gesture loses if [onSecondaryLongPressDown] has
  /// previously been triggered.
  ///
  /// If the user completed the long-press, and the gesture won, then
  /// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressCancelCallback? onSecondaryLongPressCancel;

628 629 630 631 632 633
  /// 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.
  ///
634 635 636 637 638
  /// This is equivalent to (and is called immediately after)
  /// [onSecondaryLongPressStart]. The only difference between the two is that
  /// this callback does not contain details of the position at which the
  /// pointer initially contacted the screen.
  ///
639 640 641
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
642 643
  ///  * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes
  ///    this callback at the gesture layer.
644
  final GestureLongPressCallback? onSecondaryLongPress;
645 646 647 648 649 650 651

  /// 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.
  ///
652 653 654 655 656
  /// This is equivalent to (and is called immediately before)
  /// [onSecondaryLongPress]. The only difference between the two is that this
  /// callback contains details of the position at which the pointer initially
  /// contacted the screen, whereas [onSecondaryLongPress] does not.
  ///
657 658 659
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
660 661
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes
  ///    this callback at the gesture layer.
662
  final GestureLongPressStartCallback? onSecondaryLongPressStart;
663 664 665 666 667 668

  /// A pointer has been drag-moved after a long press with a secondary button.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
669 670
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes
  ///    this callback at the gesture layer.
671
  final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
672 673 674 675

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
676 677 678 679 680
  /// This is equivalent to (and is called immediately after)
  /// [onSecondaryLongPressEnd]. The only difference between the two is that
  /// this callback does not contain details of the state of the pointer when
  /// it stopped contacting the screen.
  ///
681 682 683
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
684 685
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes
  ///    this callback at the gesture layer.
686
  final GestureLongPressUpCallback? onSecondaryLongPressUp;
687 688 689 690

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
691 692 693 694 695
  /// This is equivalent to (and is called immediately before)
  /// [onSecondaryLongPressUp]. The only difference between the two is that
  /// this callback contains details of the state of the pointer when it
  /// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not.
  ///
696 697 698
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
699 700
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes
  ///    this callback at the gesture layer.
701
  final GestureLongPressEndCallback? onSecondaryLongPressEnd;
702

703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
  /// The pointer has contacted the screen with a tertiary button, which might
  /// be the start of a long-press.
  ///
  /// This triggers after the pointer down event.
  ///
  /// If the user completes the long-press, and this gesture wins,
  /// [onTertiaryLongPressStart] will be called after this callback. Otherwise,
  /// [onTertiaryLongPressCancel] will be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [onLongPressDown], a similar callback but for a primary button.
  ///  * [onSecondaryLongPressDown], a similar callback but for a secondary button.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressDownCallback? onTertiaryLongPressDown;

  /// A pointer that previously triggered [onTertiaryLongPressDown] will not
  /// end up causing a long-press.
  ///
  /// This triggers once the gesture loses if [onTertiaryLongPressDown] has
  /// previously been triggered.
  ///
  /// If the user completed the long-press, and the gesture won, then
  /// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressCancelCallback? onTertiaryLongPressCancel;

  /// Called when a long press gesture with a tertiary 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.
  ///
  /// This is equivalent to (and is called immediately after)
  /// [onTertiaryLongPressStart]. The only difference between the two is that
  /// this callback does not contain details of the position at which the
  /// pointer initially contacted the screen.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressCallback? onTertiaryLongPress;

  /// Called when a long press gesture with a tertiary 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.
  ///
  /// This is equivalent to (and is called immediately before)
  /// [onTertiaryLongPress]. The only difference between the two is that this
  /// callback contains details of the position at which the pointer initially
  /// contacted the screen, whereas [onTertiaryLongPress] does not.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressStartCallback? onTertiaryLongPressStart;

  /// A pointer has been drag-moved after a long press with a tertiary button.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate;

  /// A pointer that has triggered a long-press with a tertiary button has
  /// stopped contacting the screen.
  ///
  /// This is equivalent to (and is called immediately after)
  /// [onTertiaryLongPressEnd]. The only difference between the two is that
  /// this callback does not contain details of the state of the pointer when
  /// it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressUpCallback? onTertiaryLongPressUp;

  /// A pointer that has triggered a long-press with a tertiary button has
  /// stopped contacting the screen.
  ///
  /// This is equivalent to (and is called immediately before)
  /// [onTertiaryLongPressUp]. The only difference between the two is that
  /// this callback contains details of the state of the pointer when it
  /// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressEndCallback? onTertiaryLongPressEnd;

812 813 814 815 816 817
  /// 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.
818
  final GestureDragDownCallback? onVerticalDragDown;
819

820 821 822 823 824 825
  /// 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.
826
  final GestureDragStartCallback? onVerticalDragStart;
827

828 829 830 831 832 833
  /// 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.
834
  final GestureDragUpdateCallback? onVerticalDragUpdate;
835

836 837 838 839 840 841 842
  /// 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.
843
  final GestureDragEndCallback? onVerticalDragEnd;
844

845 846
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
847 848 849 850
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
851
  final GestureDragCancelCallback? onVerticalDragCancel;
852

853 854 855 856 857 858
  /// 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.
859
  final GestureDragDownCallback? onHorizontalDragDown;
860

861 862 863 864 865 866
  /// 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.
867
  final GestureDragStartCallback? onHorizontalDragStart;
868

869 870 871 872 873 874
  /// 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.
875
  final GestureDragUpdateCallback? onHorizontalDragUpdate;
876

877 878 879 880 881 882 883
  /// 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.
884
  final GestureDragEndCallback? onHorizontalDragEnd;
885

886 887
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
888 889 890 891
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
892
  final GestureDragCancelCallback? onHorizontalDragCancel;
893

894 895 896 897 898 899
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
900
  final GestureDragDownCallback? onPanDown;
901

902 903 904 905 906 907
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
908
  final GestureDragStartCallback? onPanStart;
909

910 911 912 913 914 915
  /// 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.
916
  final GestureDragUpdateCallback? onPanUpdate;
917

918 919 920 921 922 923 924
  /// 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.
925
  final GestureDragEndCallback? onPanEnd;
926 927

  /// The pointer that previously triggered [onPanDown] did not complete.
928 929 930 931
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
932
  final GestureDragCancelCallback? onPanCancel;
933

934 935
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
936
  final GestureScaleStartCallback? onScaleStart;
937 938 939

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
940
  final GestureScaleUpdateCallback? onScaleUpdate;
941 942

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

945 946 947 948 949 950
  /// 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.
951
  final GestureForcePressStartCallback? onForcePressStart;
952 953 954 955 956 957 958

  /// 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.
959
  final GestureForcePressPeakCallback? onForcePressPeak;
960 961 962 963 964 965 966 967

  /// 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.
968
  final GestureForcePressUpdateCallback? onForcePressUpdate;
969 970 971 972 973

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

976
  /// How this gesture detector should behave during hit testing.
977 978 979
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
980
  final HitTestBehavior? behavior;
981

Hixie's avatar
Hixie committed
982 983 984 985 986 987 988
  /// 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;

989 990 991
  /// Determines the way that drag start behavior is handled.
  ///
  /// If set to [DragStartBehavior.start], gesture drag behavior will
992 993 994
  /// begin at the position where the drag gesture won the arena. If set to
  /// [DragStartBehavior.down] it will begin at the position where a down event
  /// is first detected.
995 996 997 998 999
  ///
  /// 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.
  ///
1000
  /// By default, the drag start behavior is [DragStartBehavior.start].
1001
  ///
1002 1003 1004
  /// Only the [DragGestureRecognizer.onStart] callbacks for the
  /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and
  /// [PanGestureRecognizer] are affected by this setting.
1005 1006 1007 1008 1009 1010
  ///
  /// See also:
  ///
  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
  final DragStartBehavior dragStartBehavior;

1011
  @override
1012
  Widget build(BuildContext context) {
1013
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
1014

1015 1016 1017 1018 1019 1020 1021
    if (onTapDown != null ||
        onTapUp != null ||
        onTap != null ||
        onTapCancel != null ||
        onSecondaryTap != null ||
        onSecondaryTapDown != null ||
        onSecondaryTapUp != null ||
1022 1023 1024 1025
        onSecondaryTapCancel != null||
        onTertiaryTapDown != null ||
        onTertiaryTapUp != null ||
        onTertiaryTapCancel != null
1026
    ) {
1027 1028
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
1029 1030 1031 1032 1033
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
1034
            ..onTapCancel = onTapCancel
1035
            ..onSecondaryTap = onSecondaryTap
1036 1037
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
1038 1039 1040 1041
            ..onSecondaryTapCancel = onSecondaryTapCancel
            ..onTertiaryTapDown = onTertiaryTapDown
            ..onTertiaryTapUp = onTertiaryTapUp
            ..onTertiaryTapCancel = onTertiaryTapCancel;
1042 1043
        },
      );
1044
    }
1045

1046
    if (onDoubleTap != null) {
1047 1048
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
1049
        (DoubleTapGestureRecognizer instance) {
1050 1051 1052 1053
          instance
            ..onDoubleTapDown = onDoubleTapDown
            ..onDoubleTap = onDoubleTap
            ..onDoubleTapCancel = onDoubleTapCancel;
1054 1055
        },
      );
1056
    }
1057

1058 1059 1060
    if (onLongPressDown != null ||
        onLongPressCancel != null ||
        onLongPress != null ||
1061 1062
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
1063
        onLongPressUp != null ||
1064
        onLongPressEnd != null ||
1065 1066
        onSecondaryLongPressDown != null ||
        onSecondaryLongPressCancel != null ||
1067
        onSecondaryLongPress != null ||
1068 1069
        onSecondaryLongPressStart != null ||
        onSecondaryLongPressMoveUpdate != null ||
1070 1071 1072 1073 1074 1075 1076 1077 1078
        onSecondaryLongPressUp != null ||
        onSecondaryLongPressEnd != null ||
        onTertiaryLongPressDown != null ||
        onTertiaryLongPressCancel != null ||
        onTertiaryLongPress != null ||
        onTertiaryLongPressStart != null ||
        onTertiaryLongPressMoveUpdate != null ||
        onTertiaryLongPressUp != null ||
        onTertiaryLongPressEnd != null) {
1079 1080 1081 1082
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
1083 1084
            ..onLongPressDown = onLongPressDown
            ..onLongPressCancel = onLongPressCancel
1085 1086 1087 1088
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressUp = onLongPressUp
1089 1090 1091
            ..onLongPressEnd = onLongPressEnd
            ..onSecondaryLongPressDown = onSecondaryLongPressDown
            ..onSecondaryLongPressCancel = onSecondaryLongPressCancel
1092 1093 1094
            ..onSecondaryLongPress = onSecondaryLongPress
            ..onSecondaryLongPressStart = onSecondaryLongPressStart
            ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
1095
            ..onSecondaryLongPressUp = onSecondaryLongPressUp
1096
            ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
1097 1098 1099 1100 1101 1102 1103
            ..onTertiaryLongPressDown = onTertiaryLongPressDown
            ..onTertiaryLongPressCancel = onTertiaryLongPressCancel
            ..onTertiaryLongPress = onTertiaryLongPress
            ..onTertiaryLongPressStart = onTertiaryLongPressStart
            ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
            ..onTertiaryLongPressUp = onTertiaryLongPressUp
            ..onTertiaryLongPressEnd = onTertiaryLongPressEnd;
1104 1105 1106 1107
        },
      );
    }

1108 1109 1110 1111 1112
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
1113 1114
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
1115 1116 1117 1118 1119 1120
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
1121 1122
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior = dragStartBehavior;
1123 1124
        },
      );
1125
    }
1126

1127 1128 1129 1130 1131
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
1132 1133
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
1134 1135 1136 1137 1138 1139
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
1140 1141
            ..onCancel = onHorizontalDragCancel
            ..dragStartBehavior = dragStartBehavior;
1142 1143
        },
      );
1144
    }
1145

1146 1147 1148 1149 1150
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
1151 1152
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
1153 1154 1155 1156 1157 1158
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
1159 1160
            ..onCancel = onPanCancel
            ..dragStartBehavior = dragStartBehavior;
1161 1162
        },
      );
1163
    }
1164

1165
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
1166 1167
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
1168 1169 1170 1171
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
1172 1173
            ..onEnd = onScaleEnd
            ..dragStartBehavior = dragStartBehavior;
1174 1175
        },
      );
1176
    }
1177

1178 1179 1180 1181 1182
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
1183 1184 1185 1186 1187 1188 1189
        () => ForcePressGestureRecognizer(debugOwner: this),
        (ForcePressGestureRecognizer instance) {
          instance
            ..onStart = onForcePressStart
            ..onPeak = onForcePressPeak
            ..onUpdate = onForcePressUpdate
            ..onEnd = onForcePressEnd;
1190 1191 1192 1193
        },
      );
    }

1194
    return RawGestureDetector(
1195 1196 1197
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
1198
      child: child,
1199
    );
1200
  }
1201

1202 1203 1204 1205 1206
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
1207
}
1208

1209 1210 1211 1212
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
1213
/// [RawGestureDetector] is useful primarily when developing your
1214
/// own gesture recognizers.
1215 1216 1217 1218
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
1219
/// {@tool snippet}
1220 1221 1222 1223 1224 1225
///
/// 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
1226
/// RawGestureDetector(
1227
///   gestures: <Type, GestureRecognizerFactory>{
1228 1229
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
1230 1231
///       (TapGestureRecognizer instance) {
///         instance
1232 1233 1234 1235
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
1236 1237 1238
///       },
///     ),
///   },
1239
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
1240 1241
/// )
/// ```
1242
/// {@end-tool}
1243 1244 1245 1246
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
1247
///  * [Listener], a widget that reports raw pointer events.
1248
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
1249
class RawGestureDetector extends StatefulWidget {
1250 1251
  /// Creates a widget that detects gestures.
  ///
1252 1253 1254
  /// 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].
1255
  const RawGestureDetector({
1256
    Key? key,
1257
    this.child,
1258
    this.gestures = const <Type, GestureRecognizerFactory>{},
1259
    this.behavior,
1260
    this.excludeFromSemantics = false,
1261
    this.semantics,
1262 1263 1264
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
1265

1266
  /// The widget below this widget in the tree.
1267
  ///
1268
  /// {@macro flutter.widgets.ProxyWidget.child}
1269
  final Widget? child;
1270

1271
  /// The gestures that this widget will attempt to recognize.
1272 1273 1274 1275 1276 1277
  ///
  /// 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].
1278 1279 1280
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
1281 1282 1283
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
1284
  final HitTestBehavior? behavior;
1285 1286 1287 1288 1289 1290 1291 1292

  /// 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;

1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
  /// 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
1305 1306
  ///    `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd`
  ///    and `onLongPressUp`.
1307 1308 1309 1310 1311 1312 1313
  ///  * 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`.
  ///
1314
  /// {@tool snippet}
1315 1316 1317 1318 1319 1320
  /// 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({
1321
  ///     Key? key,
1322 1323
  ///     required this.child,
  ///     required this.onForcePress,
1324
  ///   }) : super(key: key);
1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358
  ///
  ///   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}
1359
  final SemanticsGestureDelegate? semantics;
1360

1361
  @override
1362
  RawGestureDetectorState createState() => RawGestureDetectorState();
1363 1364
}

1365
/// State for a [RawGestureDetector].
1366
class RawGestureDetectorState extends State<RawGestureDetector> {
1367 1368
  Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
  SemanticsGestureDelegate? _semantics;
1369

1370
  @override
1371 1372
  void initState() {
    super.initState();
1373
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
1374
    _syncAll(widget.gestures);
1375 1376
  }

1377
  @override
1378
  void didUpdateWidget(RawGestureDetector oldWidget) {
1379
    super.didUpdateWidget(oldWidget);
1380 1381 1382
    if (!(oldWidget.semantics == null && widget.semantics == null)) {
      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    }
1383
    _syncAll(widget.gestures);
1384 1385
  }

1386
  /// This method can be called after the build phase, during the
1387
  /// layout of the nearest descendant [RenderObjectWidget] of the
1388 1389 1390 1391 1392 1393 1394
  /// 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.
1395 1396 1397 1398
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
1399 1400
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
1401
      if (!context.findRenderObject()!.owner!.debugDoingLayout) {
1402 1403 1404 1405 1406 1407
        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 '
1408
            'RawGestureDetector or GestureDetector object.',
1409
          ),
1410
        ]);
1411
      }
1412
      return true;
1413
    }());
1414
    _syncAll(gestures);
1415
    if (!widget.excludeFromSemantics) {
1416
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler;
1417
      _updateSemanticsForRenderObject(semanticsGestureHandler);
1418 1419 1420
    }
  }

1421 1422
  /// This method can be called to filter the list of available semantic actions,
  /// after the render object was created.
1423 1424 1425
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
1426 1427 1428 1429 1430
  ///
  /// 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
1431
  /// actions to filter changes, it must be called again.
1432
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
1433 1434 1435
    if (widget.excludeFromSemantics)
      return;

1436
    final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?;
1437
    assert(() {
1438
      if (semanticsGestureHandler == null) {
1439
        throw FlutterError(
1440
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
1441
          'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.',
1442 1443 1444
        );
      }
      return true;
1445
    }());
1446

1447
    semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
1448 1449
  }

1450
  @override
1451
  void dispose() {
1452
    for (final GestureRecognizer recognizer in _recognizers!.values)
1453 1454 1455
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
1456 1457
  }

1458 1459
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
1460
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
1461
    _recognizers = <Type, GestureRecognizer>{};
1462
    for (final Type type in gestures.keys) {
1463
      assert(gestures[type] != null);
1464 1465 1466 1467 1468
      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]!);
1469
    }
1470
    for (final Type type in oldRecognizers.keys) {
1471 1472
      if (!_recognizers!.containsKey(type))
        oldRecognizers[type]!.dispose();
1473
    }
1474 1475
  }

Ian Hickson's avatar
Ian Hickson committed
1476
  void _handlePointerDown(PointerDownEvent event) {
1477
    assert(_recognizers != null);
1478
    for (final GestureRecognizer recognizer in _recognizers!.values)
1479
      recognizer.addPointer(event);
1480 1481
  }

1482
  HitTestBehavior get _defaultBehavior {
1483
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
1484 1485
  }

1486 1487 1488
  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
    assert(!widget.excludeFromSemantics);
    assert(_semantics != null);
1489
    _semantics!.assignSemantics(renderObject);
1490 1491
  }

1492
  @override
1493
  Widget build(BuildContext context) {
1494
    Widget result = Listener(
1495 1496
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
1497
      child: widget.child,
1498
    );
1499
    if (!widget.excludeFromSemantics) {
1500
      result = _GestureSemantics(
1501
        behavior: widget.behavior ?? _defaultBehavior,
1502
        assignSemantics: _updateSemanticsForRenderObject,
1503
        child: result,
1504
      );
1505
    }
1506 1507
    return result;
  }
Hixie's avatar
Hixie committed
1508

1509
  @override
1510 1511
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1512
    if (_recognizers == null) {
1513
      properties.add(DiagnosticsNode.message('DISPOSED'));
1514
    } else {
1515
      final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
1516
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
1517
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers!.values, level: DiagnosticLevel.fine));
1518 1519 1520 1521
      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
      if (!widget.excludeFromSemantics) {
        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
      }
1522
    }
1523
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1524 1525 1526
  }
}

1527 1528
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);

1529 1530
class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
1531 1532
    Key? key,
    Widget? child,
1533
    required this.behavior,
1534
    required this.assignSemantics,
1535 1536
  }) : assert(assignSemantics != null),
       super(key: key, child: child);
1537

1538
  final HitTestBehavior behavior;
1539
  final _AssignSemantics assignSemantics;
1540 1541 1542

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1543 1544
    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler()
      ..behavior = behavior;
1545 1546
    assignSemantics(renderObject);
    return renderObject;
1547 1548
  }

1549 1550
  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
1551
    renderObject.behavior = behavior;
1552
    assignSemantics(renderObject);
Hixie's avatar
Hixie committed
1553
  }
1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567
}

/// 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
1568
  /// [RawGestureDetectorState.replaceGestureRecognizers].
1569
  void assignSemantics(RenderSemanticsGestureHandler renderObject);
1570 1571

  @override
1572
  String toString() => '${objectRuntimeType(this, 'SemanticsGestureDelegate')}()';
1573 1574 1575 1576 1577 1578 1579 1580
}

// 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
1581
// this way in order to work independently in a [RawGestureRecognizer] to
1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593
// 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);
1594
    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!;
1595 1596 1597 1598 1599
    renderObject
      ..onTap = _getTapHandler(recognizers)
      ..onLongPress = _getLongPressHandler(recognizers)
      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1600
  }
1601

1602 1603
  GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
    final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?;
1604 1605 1606 1607 1608
    if (tap == null)
      return null;

    return () {
      assert(tap != null);
1609 1610 1611
      tap.onTapDown?.call(TapDownDetails());
      tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown));
      tap.onTap?.call();
1612
    };
1613
  }
1614

1615 1616
  GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
    final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?;
1617 1618 1619 1620
    if (longPress == null)
      return null;

    return () {
1621
      longPress.onLongPressDown?.call(const LongPressDownDetails());
1622 1623 1624 1625
      longPress.onLongPressStart?.call(const LongPressStartDetails());
      longPress.onLongPress?.call();
      longPress.onLongPressEnd?.call(const LongPressEndDetails());
      longPress.onLongPressUp?.call();
1626
    };
1627
  }
1628

1629 1630 1631
  GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1632

1633
    final GestureDragUpdateCallback? horizontalHandler = horizontal == null ?
1634 1635
      null :
      (DragUpdateDetails details) {
1636 1637 1638 1639
        horizontal.onDown?.call(DragDownDetails());
        horizontal.onStart?.call(DragStartDetails());
        horizontal.onUpdate?.call(details);
        horizontal.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1640 1641
      };

1642
    final GestureDragUpdateCallback? panHandler = pan == null ?
1643 1644
      null :
      (DragUpdateDetails details) {
1645 1646 1647 1648
        pan.onDown?.call(DragDownDetails());
        pan.onStart?.call(DragStartDetails());
        pan.onUpdate?.call(details);
        pan.onEnd?.call(DragEndDetails());
1649 1650 1651 1652 1653 1654 1655 1656 1657 1658
      };

    if (horizontalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (horizontalHandler != null)
        horizontalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1659
  }
1660

1661 1662 1663
  GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1664

1665
    final GestureDragUpdateCallback? verticalHandler = vertical == null ?
1666 1667
      null :
      (DragUpdateDetails details) {
1668 1669 1670 1671
        vertical.onDown?.call(DragDownDetails());
        vertical.onStart?.call(DragStartDetails());
        vertical.onUpdate?.call(details);
        vertical.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1672 1673
      };

1674
    final GestureDragUpdateCallback? panHandler = pan == null ?
1675 1676
      null :
      (DragUpdateDetails details) {
1677 1678 1679 1680
        pan.onDown?.call(DragDownDetails());
        pan.onStart?.call(DragStartDetails());
        pan.onUpdate?.call(details);
        pan.onEnd?.call(DragEndDetails());
1681 1682 1683 1684 1685 1686 1687 1688 1689 1690
      };

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