gesture_detector.dart 63.4 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
import 'media_query.dart';
12

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

51
// Examples can assume:
52
// late bool _lights;
53
// void setState(VoidCallback fn) { }
54 55
// late String _last;
// late 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
/// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4}
///
120 121 122
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
123 124 125 126
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
127
/// See <http://flutter.dev/gestures/> for additional information.
128 129 130 131
///
/// 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.
132
///
133
/// {@tool dartpad}
134 135 136
/// 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.
137
///
138
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.0.dart **
139
/// {@end-tool}
140
///
141
/// {@tool dartpad}
142 143
/// This example uses a [Container] that wraps a [GestureDetector] widget which
/// detects a tap.
144
///
145 146 147 148
/// 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.
149
///
150
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart **
151 152
/// {@end-tool}
///
153 154 155 156
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
157 158 159 160 161 162
///
/// 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.
163
///  * [RawGestureDetector], a widget that is used to detect custom gestures.
164
class GestureDetector extends StatelessWidget {
165 166 167 168 169 170 171 172 173
  /// 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.
  ///
174 175
  /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4}
  ///
176 177
  /// By default, gesture detectors contribute semantic information to the tree
  /// that is used by assistive technology.
178
  GestureDetector({
179
    Key? key,
180
    this.child,
181
    this.onTapDown,
182 183
    this.onTapUp,
    this.onTap,
184
    this.onTapCancel,
185
    this.onSecondaryTap,
186 187 188
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,
189 190 191
    this.onTertiaryTapDown,
    this.onTertiaryTapUp,
    this.onTertiaryTapCancel,
192
    this.onDoubleTapDown,
193
    this.onDoubleTap,
194
    this.onDoubleTapCancel,
195 196
    this.onLongPressDown,
    this.onLongPressCancel,
197
    this.onLongPress,
198 199
    this.onLongPressStart,
    this.onLongPressMoveUpdate,
200
    this.onLongPressUp,
201
    this.onLongPressEnd,
202 203
    this.onSecondaryLongPressDown,
    this.onSecondaryLongPressCancel,
204 205 206 207 208
    this.onSecondaryLongPress,
    this.onSecondaryLongPressStart,
    this.onSecondaryLongPressMoveUpdate,
    this.onSecondaryLongPressUp,
    this.onSecondaryLongPressEnd,
209 210 211 212 213 214 215
    this.onTertiaryLongPressDown,
    this.onTertiaryLongPressCancel,
    this.onTertiaryLongPress,
    this.onTertiaryLongPressStart,
    this.onTertiaryLongPressMoveUpdate,
    this.onTertiaryLongPressUp,
    this.onTertiaryLongPressEnd,
216
    this.onVerticalDragDown,
217 218 219
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
220 221
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
222 223 224
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
225
    this.onHorizontalDragCancel,
226 227 228 229
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
230
    this.onPanDown,
231 232
    this.onPanStart,
    this.onPanUpdate,
233
    this.onPanEnd,
234
    this.onPanCancel,
235 236
    this.onScaleStart,
    this.onScaleUpdate,
237
    this.onScaleEnd,
Hixie's avatar
Hixie committed
238
    this.behavior,
239
    this.excludeFromSemantics = false,
240
    this.dragStartBehavior = DragStartBehavior.start,
241
  }) : assert(excludeFromSemantics != null),
242
       assert(dragStartBehavior != null),
243 244 245 246 247 248 249
       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) {
250 251 252
             throw FlutterError.fromParts(<DiagnosticsNode>[
               ErrorSummary('Incorrect GestureDetector arguments.'),
               ErrorDescription(
253
                 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',
254
               ),
255
               ErrorHint('Just use the scale gesture recognizer.'),
256
             ]);
257 258 259
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
260 261 262
             throw FlutterError(
               'Incorrect GestureDetector arguments.\n'
               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
263
               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.',
264
             );
265 266 267
           }
         }
         return true;
268
       }()),
269
       super(key: key);
270

271
  /// The widget below this widget in the tree.
272
  ///
273
  /// {@macro flutter.widgets.ProxyWidget.child}
274
  final Widget? child;
275

276 277
  /// A pointer that might cause a tap with a primary button has contacted the
  /// screen at a particular location.
278 279 280 281
  ///
  /// 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.
282 283 284 285
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
286
  final GestureTapDownCallback? onTapDown;
287

288 289
  /// A pointer that will trigger a tap with a primary button has stopped
  /// contacting the screen at a particular location.
290 291 292
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
293 294 295 296
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
297
  final GestureTapUpCallback? onTapUp;
298

299
  /// A tap with a primary button has occurred.
300 301 302 303 304 305
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
306
  ///  * [kPrimaryButton], the button this callback responds to.
307 308
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
309
  final GestureTapCallback? onTap;
310

311 312
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
313 314 315
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
316 317 318 319
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
320
  final GestureTapCancelCallback? onTapCancel;
321

322 323 324 325 326 327 328 329 330 331
  /// 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.
332
  final GestureTapCallback? onSecondaryTap;
333

334 335 336 337 338 339 340 341 342 343
  /// 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.
344
  final GestureTapDownCallback? onSecondaryTapDown;
345 346 347 348 349 350 351 352 353

  /// 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:
  ///
354 355
  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
  ///    pass any details about the tap.
356
  ///  * [kSecondaryButton], the button this callback responds to.
357
  final GestureTapUpCallback? onSecondaryTapUp;
358 359 360 361 362 363 364 365 366 367

  /// 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.
368
  final GestureTapCancelCallback? onSecondaryTapCancel;
369

370 371 372 373 374 375 376 377 378 379
  /// 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.
380
  final GestureTapDownCallback? onTertiaryTapDown;
381 382 383 384 385 386 387 388 389 390

  /// 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.
391
  final GestureTapUpCallback? onTertiaryTapUp;
392 393 394 395 396 397 398 399 400 401

  /// 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.
402
  final GestureTapCancelCallback? onTertiaryTapCancel;
403

404 405 406 407 408 409 410 411 412 413 414 415
  /// 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.
416
  final GestureTapDownCallback? onDoubleTapDown;
417

418 419 420 421 422 423
  /// 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.
424
  final GestureTapCallback? onDoubleTap;
425

426 427 428 429 430 431
  /// The pointer that previously triggered [onDoubleTapDown] will not end up
  /// causing a double tap.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
432
  final GestureTapCancelCallback? onDoubleTapCancel;
433

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
  /// 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;

468
  /// Called when a long press gesture with a primary button has been recognized.
469 470 471 472
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
473 474 475 476 477
  /// 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.
  ///
478 479
  /// See also:
  ///
480
  ///  * [kPrimaryButton], the button this callback responds to.
481 482
  ///  * [LongPressGestureRecognizer.onLongPress], which exposes this
  ///    callback at the gesture layer.
483
  final GestureLongPressCallback? onLongPress;
484

485
  /// Called when a long press gesture with a primary button has been recognized.
486 487 488 489
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
490 491 492 493 494
  /// 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.
  ///
495 496
  /// See also:
  ///
497
  ///  * [kPrimaryButton], the button this callback responds to.
498 499
  ///  * [LongPressGestureRecognizer.onLongPressStart], which exposes this
  ///    callback at the gesture layer.
500
  final GestureLongPressStartCallback? onLongPressStart;
501

502
  /// A pointer has been drag-moved after a long-press with a primary button.
503 504 505 506
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
507 508
  ///  * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this
  ///    callback at the gesture layer.
509
  final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
510

511 512
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
513
  ///
514 515 516 517 518
  /// 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.
  ///
519 520
  /// See also:
  ///
521
  ///  * [kPrimaryButton], the button this callback responds to.
522 523
  ///  * [LongPressGestureRecognizer.onLongPressUp], which exposes this
  ///    callback at the gesture layer.
524
  final GestureLongPressUpCallback? onLongPressUp;
525

526 527
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
528
  ///
529 530 531 532 533
  /// 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.
  ///
534 535
  /// See also:
  ///
536
  ///  * [kPrimaryButton], the button this callback responds to.
537 538
  ///  * [LongPressGestureRecognizer.onLongPressEnd], which exposes this
  ///    callback at the gesture layer.
539
  final GestureLongPressEndCallback? onLongPressEnd;
540

541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  /// 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;

575 576 577 578 579 580
  /// 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.
  ///
581 582 583 584 585
  /// 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.
  ///
586 587 588
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
589 590
  ///  * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes
  ///    this callback at the gesture layer.
591
  final GestureLongPressCallback? onSecondaryLongPress;
592 593 594 595 596 597 598

  /// 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.
  ///
599 600 601 602 603
  /// 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.
  ///
604 605 606
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
607 608
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes
  ///    this callback at the gesture layer.
609
  final GestureLongPressStartCallback? onSecondaryLongPressStart;
610 611 612 613 614 615

  /// A pointer has been drag-moved after a long press with a secondary button.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
616 617
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes
  ///    this callback at the gesture layer.
618
  final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
619 620 621 622

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
623 624 625 626 627
  /// 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.
  ///
628 629 630
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
631 632
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes
  ///    this callback at the gesture layer.
633
  final GestureLongPressUpCallback? onSecondaryLongPressUp;
634 635 636 637

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
638 639 640 641 642
  /// 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.
  ///
643 644 645
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
646 647
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes
  ///    this callback at the gesture layer.
648
  final GestureLongPressEndCallback? onSecondaryLongPressEnd;
649

650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 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
  /// 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;

759 760 761 762 763 764
  /// 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.
765
  final GestureDragDownCallback? onVerticalDragDown;
766

767 768 769 770 771 772
  /// 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.
773
  final GestureDragStartCallback? onVerticalDragStart;
774

775 776 777 778 779 780
  /// 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.
781
  final GestureDragUpdateCallback? onVerticalDragUpdate;
782

783 784 785 786 787 788 789
  /// 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.
790
  final GestureDragEndCallback? onVerticalDragEnd;
791

792 793
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
794 795 796 797
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
798
  final GestureDragCancelCallback? onVerticalDragCancel;
799

800 801 802 803 804 805
  /// 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.
806
  final GestureDragDownCallback? onHorizontalDragDown;
807

808 809 810 811 812 813
  /// 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.
814
  final GestureDragStartCallback? onHorizontalDragStart;
815

816 817 818 819 820 821
  /// 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.
822
  final GestureDragUpdateCallback? onHorizontalDragUpdate;
823

824 825 826 827 828 829 830
  /// 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.
831
  final GestureDragEndCallback? onHorizontalDragEnd;
832

833 834
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
835 836 837 838
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
839
  final GestureDragCancelCallback? onHorizontalDragCancel;
840

841 842 843 844 845 846
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
847
  final GestureDragDownCallback? onPanDown;
848

849 850 851 852 853 854
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
855
  final GestureDragStartCallback? onPanStart;
856

857 858 859 860 861 862
  /// 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.
863
  final GestureDragUpdateCallback? onPanUpdate;
864

865 866 867 868 869 870 871
  /// 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.
872
  final GestureDragEndCallback? onPanEnd;
873 874

  /// The pointer that previously triggered [onPanDown] did not complete.
875 876 877 878
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
879
  final GestureDragCancelCallback? onPanCancel;
880

881 882
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
883
  final GestureScaleStartCallback? onScaleStart;
884 885 886

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
887
  final GestureScaleUpdateCallback? onScaleUpdate;
888 889

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

892 893 894 895 896 897
  /// 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.
898
  final GestureForcePressStartCallback? onForcePressStart;
899 900 901 902 903 904 905

  /// 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.
906
  final GestureForcePressPeakCallback? onForcePressPeak;
907 908 909 910 911 912 913 914

  /// 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.
915
  final GestureForcePressUpdateCallback? onForcePressUpdate;
916 917 918 919 920

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

923
  /// How this gesture detector should behave during hit testing.
924 925 926
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
927
  final HitTestBehavior? behavior;
928

Hixie's avatar
Hixie committed
929 930 931 932 933 934 935
  /// 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;

936 937 938
  /// Determines the way that drag start behavior is handled.
  ///
  /// If set to [DragStartBehavior.start], gesture drag behavior will
939 940 941
  /// 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.
942 943 944 945 946
  ///
  /// 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.
  ///
947
  /// By default, the drag start behavior is [DragStartBehavior.start].
948
  ///
949 950 951
  /// Only the [DragGestureRecognizer.onStart] callbacks for the
  /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and
  /// [PanGestureRecognizer] are affected by this setting.
952 953 954 955 956 957
  ///
  /// See also:
  ///
  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
  final DragStartBehavior dragStartBehavior;

958
  @override
959
  Widget build(BuildContext context) {
960
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
961
    final DeviceGestureSettings? gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
962

963 964 965 966 967 968 969
    if (onTapDown != null ||
        onTapUp != null ||
        onTap != null ||
        onTapCancel != null ||
        onSecondaryTap != null ||
        onSecondaryTapDown != null ||
        onSecondaryTapUp != null ||
970 971 972 973
        onSecondaryTapCancel != null||
        onTertiaryTapDown != null ||
        onTertiaryTapUp != null ||
        onTertiaryTapCancel != null
974
    ) {
975 976
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
977 978 979 980 981
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
982
            ..onTapCancel = onTapCancel
983
            ..onSecondaryTap = onSecondaryTap
984 985
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
986 987 988
            ..onSecondaryTapCancel = onSecondaryTapCancel
            ..onTertiaryTapDown = onTertiaryTapDown
            ..onTertiaryTapUp = onTertiaryTapUp
989 990
            ..onTertiaryTapCancel = onTertiaryTapCancel
            ..gestureSettings = gestureSettings;
991 992
        },
      );
993
    }
994

995
    if (onDoubleTap != null) {
996 997
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
998
        (DoubleTapGestureRecognizer instance) {
999 1000 1001
          instance
            ..onDoubleTapDown = onDoubleTapDown
            ..onDoubleTap = onDoubleTap
1002 1003
            ..onDoubleTapCancel = onDoubleTapCancel
            ..gestureSettings = gestureSettings;
1004 1005
        },
      );
1006
    }
1007

1008 1009 1010
    if (onLongPressDown != null ||
        onLongPressCancel != null ||
        onLongPress != null ||
1011 1012
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
1013
        onLongPressUp != null ||
1014
        onLongPressEnd != null ||
1015 1016
        onSecondaryLongPressDown != null ||
        onSecondaryLongPressCancel != null ||
1017
        onSecondaryLongPress != null ||
1018 1019
        onSecondaryLongPressStart != null ||
        onSecondaryLongPressMoveUpdate != null ||
1020 1021 1022 1023 1024 1025 1026 1027 1028
        onSecondaryLongPressUp != null ||
        onSecondaryLongPressEnd != null ||
        onTertiaryLongPressDown != null ||
        onTertiaryLongPressCancel != null ||
        onTertiaryLongPress != null ||
        onTertiaryLongPressStart != null ||
        onTertiaryLongPressMoveUpdate != null ||
        onTertiaryLongPressUp != null ||
        onTertiaryLongPressEnd != null) {
1029 1030 1031 1032
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
1033 1034
            ..onLongPressDown = onLongPressDown
            ..onLongPressCancel = onLongPressCancel
1035 1036 1037 1038
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressUp = onLongPressUp
1039 1040 1041
            ..onLongPressEnd = onLongPressEnd
            ..onSecondaryLongPressDown = onSecondaryLongPressDown
            ..onSecondaryLongPressCancel = onSecondaryLongPressCancel
1042 1043 1044
            ..onSecondaryLongPress = onSecondaryLongPress
            ..onSecondaryLongPressStart = onSecondaryLongPressStart
            ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
1045
            ..onSecondaryLongPressUp = onSecondaryLongPressUp
1046
            ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
1047 1048 1049 1050 1051 1052
            ..onTertiaryLongPressDown = onTertiaryLongPressDown
            ..onTertiaryLongPressCancel = onTertiaryLongPressCancel
            ..onTertiaryLongPress = onTertiaryLongPress
            ..onTertiaryLongPressStart = onTertiaryLongPressStart
            ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
            ..onTertiaryLongPressUp = onTertiaryLongPressUp
1053 1054
            ..onTertiaryLongPressEnd = onTertiaryLongPressEnd
            ..gestureSettings = gestureSettings;
1055 1056 1057 1058
        },
      );
    }

1059 1060 1061 1062 1063
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
1064 1065
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
1066 1067 1068 1069 1070 1071
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
1072
            ..onCancel = onVerticalDragCancel
1073 1074
            ..dragStartBehavior = dragStartBehavior
            ..gestureSettings = gestureSettings;
1075 1076
        },
      );
1077
    }
1078

1079 1080 1081 1082 1083
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
1084 1085
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
1086 1087 1088 1089 1090 1091
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
1092
            ..onCancel = onHorizontalDragCancel
1093 1094
            ..dragStartBehavior = dragStartBehavior
            ..gestureSettings = gestureSettings;
1095 1096
        },
      );
1097
    }
1098

1099 1100 1101 1102 1103
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
1104 1105
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
1106 1107 1108 1109 1110 1111
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
1112
            ..onCancel = onPanCancel
1113 1114
            ..dragStartBehavior = dragStartBehavior
            ..gestureSettings = gestureSettings;
1115 1116
        },
      );
1117
    }
1118

1119
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
1120 1121
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
1122 1123 1124 1125
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
1126
            ..onEnd = onScaleEnd
1127 1128
            ..dragStartBehavior = dragStartBehavior
            ..gestureSettings = gestureSettings;
1129 1130
        },
      );
1131
    }
1132

1133 1134 1135 1136 1137
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
1138 1139 1140 1141 1142 1143
        () => ForcePressGestureRecognizer(debugOwner: this),
        (ForcePressGestureRecognizer instance) {
          instance
            ..onStart = onForcePressStart
            ..onPeak = onForcePressPeak
            ..onUpdate = onForcePressUpdate
1144 1145
            ..onEnd = onForcePressEnd
            ..gestureSettings = gestureSettings;
1146 1147 1148 1149
        },
      );
    }

1150
    return RawGestureDetector(
1151 1152 1153
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
1154
      child: child,
1155
    );
1156
  }
1157

1158 1159 1160 1161 1162
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
1163
}
1164

1165 1166 1167 1168
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
1169
/// [RawGestureDetector] is useful primarily when developing your
1170
/// own gesture recognizers.
1171 1172 1173 1174
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
1175
/// {@tool snippet}
1176 1177 1178 1179 1180 1181
///
/// 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
1182
/// RawGestureDetector(
1183
///   gestures: <Type, GestureRecognizerFactory>{
1184 1185
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
1186 1187
///       (TapGestureRecognizer instance) {
///         instance
1188 1189 1190 1191
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
1192 1193 1194
///       },
///     ),
///   },
1195
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
1196 1197
/// )
/// ```
1198
/// {@end-tool}
1199 1200 1201 1202
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
1203
///  * [Listener], a widget that reports raw pointer events.
1204
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
1205
class RawGestureDetector extends StatefulWidget {
1206 1207
  /// Creates a widget that detects gestures.
  ///
1208 1209 1210
  /// 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].
1211
  const RawGestureDetector({
1212
    Key? key,
1213
    this.child,
1214
    this.gestures = const <Type, GestureRecognizerFactory>{},
1215
    this.behavior,
1216
    this.excludeFromSemantics = false,
1217
    this.semantics,
1218 1219 1220
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
1221

1222
  /// The widget below this widget in the tree.
1223
  ///
1224
  /// {@macro flutter.widgets.ProxyWidget.child}
1225
  final Widget? child;
1226

1227
  /// The gestures that this widget will attempt to recognize.
1228 1229 1230 1231 1232 1233
  ///
  /// 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].
1234 1235 1236
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
1237 1238 1239
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
1240
  final HitTestBehavior? behavior;
1241 1242 1243 1244 1245 1246 1247 1248

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

1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260
  /// 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
1261 1262
  ///    `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd`
  ///    and `onLongPressUp`.
1263 1264 1265 1266 1267 1268 1269
  ///  * 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`.
  ///
1270
  /// {@tool snippet}
1271 1272 1273 1274 1275 1276
  /// 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({
1277
  ///     Key? key,
1278 1279
  ///     required this.child,
  ///     required this.onForcePress,
1280
  ///   }) : super(key: key);
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314
  ///
  ///   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}
1315
  final SemanticsGestureDelegate? semantics;
1316

1317
  @override
1318
  RawGestureDetectorState createState() => RawGestureDetectorState();
1319 1320
}

1321
/// State for a [RawGestureDetector].
1322
class RawGestureDetectorState extends State<RawGestureDetector> {
1323 1324
  Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
  SemanticsGestureDelegate? _semantics;
1325

1326
  @override
1327 1328
  void initState() {
    super.initState();
1329
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
1330
    _syncAll(widget.gestures);
1331 1332
  }

1333
  @override
1334
  void didUpdateWidget(RawGestureDetector oldWidget) {
1335
    super.didUpdateWidget(oldWidget);
1336 1337 1338
    if (!(oldWidget.semantics == null && widget.semantics == null)) {
      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    }
1339
    _syncAll(widget.gestures);
1340 1341
  }

1342
  /// This method can be called after the build phase, during the
1343
  /// layout of the nearest descendant [RenderObjectWidget] of the
1344 1345 1346 1347 1348 1349 1350
  /// 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.
1351 1352 1353 1354
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
1355 1356
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
1357
      if (!context.findRenderObject()!.owner!.debugDoingLayout) {
1358 1359 1360 1361 1362 1363
        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 '
1364
            'RawGestureDetector or GestureDetector object.',
1365
          ),
1366
        ]);
1367
      }
1368
      return true;
1369
    }());
1370
    _syncAll(gestures);
1371
    if (!widget.excludeFromSemantics) {
1372
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler;
1373
      _updateSemanticsForRenderObject(semanticsGestureHandler);
1374 1375 1376
    }
  }

1377 1378
  /// This method can be called to filter the list of available semantic actions,
  /// after the render object was created.
1379 1380 1381
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
1382 1383 1384 1385 1386
  ///
  /// 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
1387
  /// actions to filter changes, it must be called again.
1388
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
1389 1390 1391
    if (widget.excludeFromSemantics)
      return;

1392
    final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?;
1393
    assert(() {
1394
      if (semanticsGestureHandler == null) {
1395
        throw FlutterError(
1396
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
1397
          'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.',
1398 1399 1400
        );
      }
      return true;
1401
    }());
1402

1403
    semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
1404 1405
  }

1406
  @override
1407
  void dispose() {
1408
    for (final GestureRecognizer recognizer in _recognizers!.values)
1409 1410 1411
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
1412 1413
  }

1414 1415
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
1416
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
1417
    _recognizers = <Type, GestureRecognizer>{};
1418
    for (final Type type in gestures.keys) {
1419
      assert(gestures[type] != null);
1420 1421 1422 1423 1424
      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]!);
1425
    }
1426
    for (final Type type in oldRecognizers.keys) {
1427 1428
      if (!_recognizers!.containsKey(type))
        oldRecognizers[type]!.dispose();
1429
    }
1430 1431
  }

Ian Hickson's avatar
Ian Hickson committed
1432
  void _handlePointerDown(PointerDownEvent event) {
1433
    assert(_recognizers != null);
1434
    for (final GestureRecognizer recognizer in _recognizers!.values)
1435
      recognizer.addPointer(event);
1436 1437
  }

1438
  HitTestBehavior get _defaultBehavior {
1439
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
1440 1441
  }

1442 1443 1444
  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
    assert(!widget.excludeFromSemantics);
    assert(_semantics != null);
1445
    _semantics!.assignSemantics(renderObject);
1446 1447
  }

1448
  @override
1449
  Widget build(BuildContext context) {
1450
    Widget result = Listener(
1451 1452
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
1453
      child: widget.child,
1454
    );
1455
    if (!widget.excludeFromSemantics) {
1456
      result = _GestureSemantics(
1457
        behavior: widget.behavior ?? _defaultBehavior,
1458
        assignSemantics: _updateSemanticsForRenderObject,
1459
        child: result,
1460
      );
1461
    }
1462 1463
    return result;
  }
Hixie's avatar
Hixie committed
1464

1465
  @override
1466 1467
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1468
    if (_recognizers == null) {
1469
      properties.add(DiagnosticsNode.message('DISPOSED'));
1470
    } else {
1471
      final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
1472
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
1473
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers!.values, level: DiagnosticLevel.fine));
1474 1475 1476 1477
      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
      if (!widget.excludeFromSemantics) {
        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
      }
1478
    }
1479
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1480 1481 1482
  }
}

1483 1484
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);

1485 1486
class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
1487 1488
    Key? key,
    Widget? child,
1489
    required this.behavior,
1490
    required this.assignSemantics,
1491 1492
  }) : assert(assignSemantics != null),
       super(key: key, child: child);
1493

1494
  final HitTestBehavior behavior;
1495
  final _AssignSemantics assignSemantics;
1496 1497 1498

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1499 1500
    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler()
      ..behavior = behavior;
1501 1502
    assignSemantics(renderObject);
    return renderObject;
1503 1504
  }

1505 1506
  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
1507
    renderObject.behavior = behavior;
1508
    assignSemantics(renderObject);
Hixie's avatar
Hixie committed
1509
  }
1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523
}

/// 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
1524
  /// [RawGestureDetectorState.replaceGestureRecognizers].
1525
  void assignSemantics(RenderSemanticsGestureHandler renderObject);
1526 1527

  @override
1528
  String toString() => '${objectRuntimeType(this, 'SemanticsGestureDelegate')}()';
1529 1530 1531 1532 1533 1534 1535 1536
}

// 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
1537
// this way in order to work independently in a [RawGestureRecognizer] to
1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549
// 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);
1550
    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!;
1551 1552 1553 1554 1555
    renderObject
      ..onTap = _getTapHandler(recognizers)
      ..onLongPress = _getLongPressHandler(recognizers)
      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1556
  }
1557

1558 1559
  GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
    final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?;
1560 1561 1562 1563 1564
    if (tap == null)
      return null;

    return () {
      assert(tap != null);
1565 1566 1567
      tap.onTapDown?.call(TapDownDetails());
      tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown));
      tap.onTap?.call();
1568
    };
1569
  }
1570

1571 1572
  GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
    final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?;
1573 1574 1575 1576
    if (longPress == null)
      return null;

    return () {
1577
      longPress.onLongPressDown?.call(const LongPressDownDetails());
1578 1579 1580 1581
      longPress.onLongPressStart?.call(const LongPressStartDetails());
      longPress.onLongPress?.call();
      longPress.onLongPressEnd?.call(const LongPressEndDetails());
      longPress.onLongPressUp?.call();
1582
    };
1583
  }
1584

1585 1586 1587
  GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1588

1589
    final GestureDragUpdateCallback? horizontalHandler = horizontal == null ?
1590 1591
      null :
      (DragUpdateDetails details) {
1592 1593 1594 1595
        horizontal.onDown?.call(DragDownDetails());
        horizontal.onStart?.call(DragStartDetails());
        horizontal.onUpdate?.call(details);
        horizontal.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1596 1597
      };

1598
    final GestureDragUpdateCallback? panHandler = pan == null ?
1599 1600
      null :
      (DragUpdateDetails details) {
1601 1602 1603 1604
        pan.onDown?.call(DragDownDetails());
        pan.onStart?.call(DragStartDetails());
        pan.onUpdate?.call(details);
        pan.onEnd?.call(DragEndDetails());
1605 1606 1607 1608 1609 1610 1611 1612 1613 1614
      };

    if (horizontalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (horizontalHandler != null)
        horizontalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1615
  }
1616

1617 1618 1619
  GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1620

1621
    final GestureDragUpdateCallback? verticalHandler = vertical == null ?
1622 1623
      null :
      (DragUpdateDetails details) {
1624 1625 1626 1627
        vertical.onDown?.call(DragDownDetails());
        vertical.onStart?.call(DragStartDetails());
        vertical.onUpdate?.call(details);
        vertical.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1628 1629
      };

1630
    final GestureDragUpdateCallback? panHandler = pan == null ?
1631 1632
      null :
      (DragUpdateDetails details) {
1633 1634 1635 1636
        pan.onDown?.call(DragDownDetails());
        pan.onStart?.call(DragStartDetails());
        pan.onUpdate?.call(details);
        pan.onEnd?.call(DragEndDetails());
1637 1638 1639 1640 1641 1642 1643 1644 1645 1646
      };

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