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

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

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

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

50 51 52 53 54
// Examples can assume:
// bool _lights;
// void setState(VoidCallback fn) { }
// String _last;

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

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

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

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

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

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

109 110
/// A widget that detects gestures.
///
111
/// Attempts to recognize gestures that correspond to its non-null callbacks.
112
///
113 114 115
/// 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.
///
116 117 118
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
119 120 121 122
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
123
/// See <http://flutter.dev/gestures/> for additional information.
124 125 126 127
///
/// 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.
128
///
129 130
/// {@animation 200 150 https://flutter.github.io/assets-for-api-docs/assets/widgets/gesture_detector.mp4}
///
131
/// {@tool sample}
132
///
133 134
/// This example turns the light bulb yellow when the "turn lights on" button is
/// tapped by setting the `_lights` field:
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
/// Container(
///   alignment: FractionalOffset.center,
///   color: Colors.white,
///   child: Column(
///     mainAxisAlignment: MainAxisAlignment.center,
///     children: <Widget>[
///       Padding(
///         padding: const EdgeInsets.all(8.0),
///         child: Icon(
///           Icons.lightbulb_outline,
///           color: _lights ? Colors.yellow.shade600 : Colors.black,
///           size: 60,
///         ),
///       ),
///       GestureDetector(
///         onTap: () {
///           setState(() {
///             _lights = true;
///           });
///         },
///         child: Container(
///           color: Colors.yellow.shade600,
///           padding: const EdgeInsets.all(8),
///           child: const Text('TURN LIGHTS ON'),
///         ),
///       ),
///     ],
164 165 166
///   ),
/// )
/// ```
167
/// {@end-tool}
168 169 170 171 172
///
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
173
class GestureDetector extends StatelessWidget {
174 175 176 177 178 179 180 181 182 183 184
  /// 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.
185
  GestureDetector({
186 187
    Key key,
    this.child,
188
    this.onTapDown,
189 190
    this.onTapUp,
    this.onTap,
191
    this.onTapCancel,
192 193 194
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,
195
    this.onDoubleTap,
196
    this.onLongPress,
197 198
    this.onLongPressStart,
    this.onLongPressMoveUpdate,
199
    this.onLongPressUp,
200
    this.onLongPressEnd,
201
    this.onVerticalDragDown,
202 203 204
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
205 206
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
207 208 209
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
210
    this.onHorizontalDragCancel,
211 212 213 214
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
215
    this.onPanDown,
216 217
    this.onPanStart,
    this.onPanUpdate,
218
    this.onPanEnd,
219
    this.onPanCancel,
220 221
    this.onScaleStart,
    this.onScaleUpdate,
222
    this.onScaleEnd,
Hixie's avatar
Hixie committed
223
    this.behavior,
224
    this.excludeFromSemantics = false,
225
    this.dragStartBehavior = DragStartBehavior.start,
226
  }) : assert(excludeFromSemantics != null),
227
       assert(dragStartBehavior != null),
228 229 230 231 232 233 234
       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) {
235 236 237 238 239 240 241
             throw FlutterError.fromParts(<DiagnosticsNode>[
               ErrorSummary('Incorrect GestureDetector arguments.'),
               ErrorDescription(
                 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.'
               ),
               ErrorHint('Just use the scale gesture recognizer.')
             ]);
242 243 244
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
245 246 247 248 249 250 251
             throw FlutterError.fromParts(<DiagnosticsNode>[
               ErrorSummary('Incorrect GestureDetector arguments.'),
               ErrorDescription(
                 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
                 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'
               )
             ]);
252 253 254
           }
         }
         return true;
255
       }()),
256
       super(key: key);
257

258
  /// The widget below this widget in the tree.
259 260
  ///
  /// {@macro flutter.widgets.child}
261
  final Widget child;
262

263 264
  /// A pointer that might cause a tap with a primary button has contacted the
  /// screen at a particular location.
265 266 267 268
  ///
  /// 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.
269 270 271 272
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
273
  final GestureTapDownCallback onTapDown;
274

275 276
  /// A pointer that will trigger a tap with a primary button has stopped
  /// contacting the screen at a particular location.
277 278 279
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
280 281 282 283
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
Hixie's avatar
Hixie committed
284
  final GestureTapUpCallback onTapUp;
285

286
  /// A tap with a primary button has occurred.
287 288 289 290 291 292
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
293
  ///  * [kPrimaryButton], the button this callback responds to.
294 295
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
296
  final GestureTapCallback onTap;
297

298 299
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
300 301 302
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
303 304 305 306
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
307
  final GestureTapCancelCallback onTapCancel;
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
  /// 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.
  final GestureTapDownCallback onSecondaryTapDown;

  /// 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:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  final GestureTapUpCallback onSecondaryTapUp;

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

  /// 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.
349
  final GestureTapCallback onDoubleTap;
350

351
  /// Called when a long press gesture with a primary button has been recognized.
352 353 354 355 356 357
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
358 359
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressStart], which has the same timing but has gesture details.
360
  final GestureLongPressCallback onLongPress;
361

362
  /// Called when a long press gesture with a primary button has been recognized.
363 364 365 366 367 368
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
369 370
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPress], which has the same timing but without the gesture details.
371 372
  final GestureLongPressStartCallback onLongPressStart;

373 374 375 376 377
  /// A pointer has been drag-moved after a long press with a primary button.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
378 379
  final GestureLongPressMoveUpdateCallback onLongPressMoveUpdate;

380 381
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
382 383 384
  ///
  /// See also:
  ///
385 386
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressEnd], which has the same timing but has gesture details.
387 388
  final GestureLongPressUpCallback onLongPressUp;

389 390
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
391 392 393
  ///
  /// See also:
  ///
394 395 396
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressUp], which has the same timing but without the gesture
  ///    details.
397 398
  final GestureLongPressEndCallback onLongPressEnd;

399 400 401 402 403 404
  /// 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.
405 406
  final GestureDragDownCallback onVerticalDragDown;

407 408 409 410 411 412
  /// 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.
413
  final GestureDragStartCallback onVerticalDragStart;
414

415 416 417 418 419 420
  /// 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.
421
  final GestureDragUpdateCallback onVerticalDragUpdate;
422

423 424 425 426 427 428 429
  /// 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.
430 431
  final GestureDragEndCallback onVerticalDragEnd;

432 433
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
434 435 436 437
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
438 439
  final GestureDragCancelCallback onVerticalDragCancel;

440 441 442 443 444 445
  /// 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.
446 447
  final GestureDragDownCallback onHorizontalDragDown;

448 449 450 451 452 453
  /// 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.
454
  final GestureDragStartCallback onHorizontalDragStart;
455

456 457 458 459 460 461
  /// 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.
462
  final GestureDragUpdateCallback onHorizontalDragUpdate;
463

464 465 466 467 468 469 470
  /// 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.
471 472
  final GestureDragEndCallback onHorizontalDragEnd;

473 474
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
475 476 477 478
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
479 480
  final GestureDragCancelCallback onHorizontalDragCancel;

481 482 483 484 485 486
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
487
  final GestureDragDownCallback onPanDown;
488

489 490 491 492 493 494
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
495
  final GestureDragStartCallback onPanStart;
496

497 498 499 500 501 502
  /// 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.
503
  final GestureDragUpdateCallback onPanUpdate;
504

505 506 507 508 509 510 511
  /// 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.
512
  final GestureDragEndCallback onPanEnd;
513 514

  /// The pointer that previously triggered [onPanDown] did not complete.
515 516 517 518
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
519
  final GestureDragCancelCallback onPanCancel;
520

521 522
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
523
  final GestureScaleStartCallback onScaleStart;
524 525 526

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
527
  final GestureScaleUpdateCallback onScaleUpdate;
528 529

  /// The pointers are no longer in contact with the screen.
530 531
  final GestureScaleEndCallback onScaleEnd;

532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
  /// 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.
  final GestureForcePressStartCallback onForcePressStart;

  /// 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.
  final GestureForcePressPeakCallback onForcePressPeak;

  /// 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.
  final GestureForcePressUpdateCallback onForcePressUpdate;

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

563
  /// How this gesture detector should behave during hit testing.
564 565 566
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
567 568
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
569 570 571 572 573 574 575
  /// 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;

576 577 578 579 580 581 582 583 584 585
  /// Determines the way that drag start behavior is handled.
  ///
  /// If set to [DragStartBehavior.start], gesture drag behavior will
  /// begin upon the detection of a drag gesture. If set to
  /// [DragStartBehavior.down] it will begin when a down event is first detected.
  ///
  /// In general, setting this to [DragStartBehavior.start] will make drag
  /// animation smoother and setting it to [DragStartBehavior.down] will make
  /// drag behavior feel slightly more reactive.
  ///
586
  /// By default, the drag start behavior is [DragStartBehavior.start].
587 588 589 590 591 592 593 594 595 596
  ///
  /// Only the [onStart] callbacks for the [VerticalDragGestureRecognizer],
  /// [HorizontalDragGestureRecognizer] and [PanGestureRecognizer] are affected
  /// by this setting.
  ///
  /// See also:
  ///
  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
  final DragStartBehavior dragStartBehavior;

597
  @override
598
  Widget build(BuildContext context) {
599
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
600

601 602 603 604 605 606 607 608 609
    if (
      onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      onTapCancel != null ||
      onSecondaryTapDown != null ||
      onSecondaryTapUp != null ||
      onSecondaryTapCancel != null
    ) {
610 611
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
612 613 614 615 616
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
617 618 619 620
            ..onTapCancel = onTapCancel
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
            ..onSecondaryTapCancel = onSecondaryTapCancel;
621 622
        },
      );
623
    }
624

625
    if (onDoubleTap != null) {
626 627
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
628 629 630 631 632
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
633
    }
634

635 636 637 638 639
    if (onLongPress != null ||
        onLongPressUp != null ||
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
        onLongPressEnd != null) {
640 641
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
642 643
        (LongPressGestureRecognizer instance) {
          instance
644
            ..onLongPress = onLongPress
645 646 647
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressEnd =onLongPressEnd
648
            ..onLongPressUp = onLongPressUp;
649 650
        },
      );
651
    }
652

653 654 655 656 657
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
658 659
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
660 661 662 663 664 665
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
666 667
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior = dragStartBehavior;
668 669
        },
      );
670
    }
671

672 673 674 675 676
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
677 678
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
679 680 681 682 683 684
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
685 686
            ..onCancel = onHorizontalDragCancel
            ..dragStartBehavior = dragStartBehavior;
687 688
        },
      );
689
    }
690

691 692 693 694 695
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
696 697
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
698 699 700 701 702 703
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
704 705
            ..onCancel = onPanCancel
            ..dragStartBehavior = dragStartBehavior;
706 707
        },
      );
708
    }
709

710
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
711 712
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
713 714 715 716 717 718 719
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
            ..onEnd = onScaleEnd;
        },
      );
720
    }
721

722 723 724 725 726
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
727 728 729 730 731 732 733
        () => ForcePressGestureRecognizer(debugOwner: this),
        (ForcePressGestureRecognizer instance) {
          instance
            ..onStart = onForcePressStart
            ..onPeak = onForcePressPeak
            ..onUpdate = onForcePressUpdate
            ..onEnd = onForcePressEnd;
734 735 736 737
        },
      );
    }

738
    return RawGestureDetector(
739 740 741
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
742
      child: child,
743
    );
744
  }
745 746 747 748 749
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
750
}
751

752 753 754 755
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
756
/// [RawGestureDetector] is useful primarily when developing your
757
/// own gesture recognizers.
758 759 760 761
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
762
/// {@tool sample}
763 764 765 766 767 768
///
/// 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
769
/// RawGestureDetector(
770
///   gestures: <Type, GestureRecognizerFactory>{
771 772
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
773 774
///       (TapGestureRecognizer instance) {
///         instance
775 776 777 778
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
779 780 781
///       },
///     ),
///   },
782
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
783 784
/// )
/// ```
785
/// {@end-tool}
786 787 788 789
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
790
///  * [Listener], a widget that reports raw pointer events.
791
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
792
class RawGestureDetector extends StatefulWidget {
793 794
  /// Creates a widget that detects gestures.
  ///
795 796 797
  /// 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].
798
  const RawGestureDetector({
799 800
    Key key,
    this.child,
801
    this.gestures = const <Type, GestureRecognizerFactory>{},
802
    this.behavior,
803
    this.excludeFromSemantics = false,
804
    this.semantics,
805 806 807
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
808

809
  /// The widget below this widget in the tree.
810 811
  ///
  /// {@macro flutter.widgets.child}
812 813
  final Widget child;

814
  /// The gestures that this widget will attempt to recognize.
815 816 817 818 819 820
  ///
  /// 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].
821 822 823
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
824 825 826
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
827 828 829 830 831 832 833 834 835
  final HitTestBehavior behavior;

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

836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
  /// Describes the semantics notations that should be added to the underlying
  /// render object [RenderSemanticsGestureHandler].
  ///
  /// It has no effect if [excludeFromSemantics] is true.
  ///
  /// When [semantics] is null, [RawGestureDetector] will fall back to a
  /// default delegate which checks if the detector owns certain gesture
  /// recognizers and calls their callbacks if they exist:
  ///
  ///  * During a semantic tap, it calls [TapGestureRecognizer]'s
  ///    `onTapDown`, `onTapUp`, and `onTap`.
  ///  * During a semantic long press, it calls [LongPressGestureRecognizer]'s
  ///    `onLongPressStart`, `onLongPress`, `onLongPressEnd` and `onLongPressUp`.
  ///  * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s
  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
  ///  * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s
  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
  ///
  /// {@tool sample}
  /// This custom gesture detector listens to force presses, while also allows
  /// the same callback to be triggered by semantic long presses.
  ///
  /// ```dart
  /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget {
  ///   const ForcePressGestureDetectorWithSemantics({
  ///     this.child,
  ///     this.onForcePress,
  ///   });
  ///
  ///   final Widget child;
  ///   final VoidCallback onForcePress;
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     return RawGestureDetector(
  ///       gestures: <Type, GestureRecognizerFactory>{
  ///         ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
  ///           () => ForcePressGestureRecognizer(debugOwner: this),
  ///           (ForcePressGestureRecognizer instance) {
  ///             instance.onStart = (_) => onForcePress();
  ///           }
  ///         ),
  ///       },
  ///       behavior: HitTestBehavior.opaque,
  ///       semantics: _LongPressSemanticsDelegate(onForcePress),
  ///       child: child,
  ///     );
  ///   }
  /// }
  ///
  /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate {
  ///   _LongPressSemanticsDelegate(this.onLongPress);
  ///
  ///   VoidCallback onLongPress;
  ///
  ///   @override
  ///   void assignSemantics(RenderSemanticsGestureHandler renderObject) {
  ///     renderObject.onLongPress = onLongPress;
  ///   }
  /// }
  /// ```
  /// {@end-tool}
  final SemanticsGestureDelegate semantics;

902
  @override
903
  RawGestureDetectorState createState() => RawGestureDetectorState();
904 905
}

906
/// State for a [RawGestureDetector].
907 908
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};
909
  SemanticsGestureDelegate _semantics;
910

911
  @override
912 913
  void initState() {
    super.initState();
914
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
915
    _syncAll(widget.gestures);
916 917
  }

918
  @override
919
  void didUpdateWidget(RawGestureDetector oldWidget) {
920
    super.didUpdateWidget(oldWidget);
921 922 923
    if (!(oldWidget.semantics == null && widget.semantics == null)) {
      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    }
924
    _syncAll(widget.gestures);
925 926
  }

927
  /// This method can be called after the build phase, during the
928
  /// layout of the nearest descendant [RenderObjectWidget] of the
929 930 931 932 933 934 935
  /// 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.
936 937 938 939
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
940 941
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
942
      if (!context.findRenderObject().owner.debugDoingLayout) {
943 944 945 946 947 948 949 950 951
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.'),
          ErrorDescription('The replaceGestureRecognizers() method can only be called during the layout phase.'),
          ErrorHint(
            'To set the gesture recognizers at other times, trigger a new build using setState() '
            'and provide the new gesture recognizers as constructor arguments to the corresponding '
            'RawGestureDetector or GestureDetector object.'
          )
        ]);
952
      }
953
      return true;
954
    }());
955
    _syncAll(gestures);
956
    if (!widget.excludeFromSemantics) {
957
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler;
958
      _updateSemanticsForRenderObject(semanticsGestureHandler);
959 960 961
    }
  }

962 963
  /// This method can be called to filter the list of available semantic actions,
  /// after the render object was created.
964 965 966
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
967 968 969 970 971
  ///
  /// 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
972
  /// actions to filter changes, it must be called again.
973
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
974 975 976
    if (widget.excludeFromSemantics)
      return;

977
    final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler;
978
    assert(() {
979
      if (semanticsGestureHandler == null) {
980
        throw FlutterError(
981
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
982
          'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.'
983 984 985
        );
      }
      return true;
986
    }());
987 988

    semanticsGestureHandler.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
989 990
  }

991
  @override
992 993 994 995 996
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
997 998
  }

999 1000
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
1001
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
1002 1003
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
1004 1005
      assert(gestures[type] != null);
      assert(gestures[type]._debugAssertTypeMatches(type));
1006
      assert(!_recognizers.containsKey(type));
1007 1008 1009
      _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]);
1010 1011 1012 1013 1014
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
1015 1016
  }

Ian Hickson's avatar
Ian Hickson committed
1017
  void _handlePointerDown(PointerDownEvent event) {
1018 1019 1020
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
1021 1022
  }

1023
  HitTestBehavior get _defaultBehavior {
1024
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
1025 1026
  }

1027 1028 1029 1030
  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
    assert(!widget.excludeFromSemantics);
    assert(_semantics != null);
    _semantics.assignSemantics(renderObject);
1031 1032
  }

1033
  @override
1034
  Widget build(BuildContext context) {
1035
    Widget result = Listener(
1036 1037
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
1038
      child: widget.child,
1039 1040
    );
    if (!widget.excludeFromSemantics)
1041 1042 1043 1044
      result = _GestureSemantics(
        child: result,
        assignSemantics: _updateSemanticsForRenderObject,
      );
1045 1046
    return result;
  }
Hixie's avatar
Hixie committed
1047

1048
  @override
1049 1050
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1051
    if (_recognizers == null) {
1052
      properties.add(DiagnosticsNode.message('DISPOSED'));
1053 1054
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
1055 1056
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
1057 1058 1059 1060
      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
      if (!widget.excludeFromSemantics) {
        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
      }
1061
    }
1062
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1063 1064 1065
  }
}

1066 1067
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);

1068 1069 1070 1071
class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
    Key key,
    Widget child,
1072 1073 1074
    @required this.assignSemantics,
  }) : assert(assignSemantics != null),
       super(key: key, child: child);
1075

1076
  final _AssignSemantics assignSemantics;
1077 1078 1079

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1080 1081 1082
    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler();
    assignSemantics(renderObject);
    return renderObject;
1083 1084
  }

1085 1086 1087
  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
    assignSemantics(renderObject);
Hixie's avatar
Hixie committed
1088
  }
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
}

/// 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
  /// [RawGestureDetector.replaceGestureRecognizers].
  void assignSemantics(RenderSemanticsGestureHandler renderObject);
1105 1106

  @override
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
  String toString() => '$runtimeType()';
}

// 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
// this way in order to work independenly in a [RawGestureRecognizer] to
// 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);
    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers;
    renderObject
      ..onTap = _getTapHandler(recognizers)
      ..onLongPress = _getLongPressHandler(recognizers)
      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1135
  }
1136

1137
  GestureTapCallback _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
1138
    final TapGestureRecognizer tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer;
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
    if (tap == null)
      return null;
    assert(tap is TapGestureRecognizer);

    return () {
      assert(tap != null);
      if (tap.onTapDown != null)
        tap.onTapDown(TapDownDetails());
      if (tap.onTapUp != null)
        tap.onTapUp(TapUpDetails());
      if (tap.onTap != null)
        tap.onTap();
    };
1152
  }
1153

1154
  GestureLongPressCallback _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
1155
    final LongPressGestureRecognizer longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer;
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
    if (longPress == null)
      return null;

    return () {
      assert(longPress is LongPressGestureRecognizer);
      if (longPress.onLongPressStart != null)
        longPress.onLongPressStart(const LongPressStartDetails());
      if (longPress.onLongPress != null)
        longPress.onLongPress();
      if (longPress.onLongPressEnd != null)
        longPress.onLongPressEnd(const LongPressEndDetails());
      if (longPress.onLongPressUp != null)
        longPress.onLongPressUp();
    };
1170
  }
1171

1172
  GestureDragUpdateCallback _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
1173 1174
    final HorizontalDragGestureRecognizer horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer;
    final PanGestureRecognizer pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer;
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211

    final GestureDragUpdateCallback horizontalHandler = horizontal == null ?
      null :
      (DragUpdateDetails details) {
        assert(horizontal is HorizontalDragGestureRecognizer);
        if (horizontal.onDown != null)
          horizontal.onDown(DragDownDetails());
        if (horizontal.onStart != null)
          horizontal.onStart(DragStartDetails());
        if (horizontal.onUpdate != null)
          horizontal.onUpdate(details);
        if (horizontal.onEnd != null)
          horizontal.onEnd(DragEndDetails(primaryVelocity: 0.0));
      };

    final GestureDragUpdateCallback panHandler = pan == null ?
      null :
      (DragUpdateDetails details) {
        assert(pan is PanGestureRecognizer);
        if (pan.onDown != null)
          pan.onDown(DragDownDetails());
        if (pan.onStart != null)
          pan.onStart(DragStartDetails());
        if (pan.onUpdate != null)
          pan.onUpdate(details);
        if (pan.onEnd != null)
          pan.onEnd(DragEndDetails());
      };

    if (horizontalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (horizontalHandler != null)
        horizontalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1212
  }
1213

1214
  GestureDragUpdateCallback _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
1215 1216
    final VerticalDragGestureRecognizer vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer;
    final PanGestureRecognizer pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer;
1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253

    final GestureDragUpdateCallback verticalHandler = vertical == null ?
      null :
      (DragUpdateDetails details) {
        assert(vertical is VerticalDragGestureRecognizer);
        if (vertical.onDown != null)
          vertical.onDown(DragDownDetails());
        if (vertical.onStart != null)
          vertical.onStart(DragStartDetails());
        if (vertical.onUpdate != null)
          vertical.onUpdate(details);
        if (vertical.onEnd != null)
          vertical.onEnd(DragEndDetails(primaryVelocity: 0.0));
      };

    final GestureDragUpdateCallback panHandler = pan == null ?
      null :
      (DragUpdateDetails details) {
        assert(pan is PanGestureRecognizer);
        if (pan.onDown != null)
          pan.onDown(DragDownDetails());
        if (pan.onStart != null)
          pan.onStart(DragStartDetails());
        if (pan.onUpdate != null)
          pan.onUpdate(details);
        if (pan.onEnd != null)
          pan.onEnd(DragEndDetails());
      };

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