gesture_detector.dart 29.2 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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
  GestureDragDownCallback,
23 24 25
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
26
  GestureDragCancelCallback,
27 28
  GestureScaleStartCallback,
  GestureScaleUpdateCallback,
29
  GestureScaleEndCallback,
30 31 32
  ScaleStartDetails,
  ScaleUpdateDetails,
  ScaleEndDetails,
33 34
  TapDownDetails,
  TapUpDetails,
35
  Velocity;
36

37
/// Factory for creating gesture recognizers.
38
///
39
/// `T` is the type of gesture recognizer this class manages.
40 41
///
/// Used by [RawGestureDetector.gestures].
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
@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].
64
typedef T GestureRecognizerFactoryConstructor<T extends GestureRecognizer>();
65 66

/// Signature for closures that implement [GestureRecognizerFactory.initializer].
67
typedef void GestureRecognizerFactoryInitializer<T extends GestureRecognizer>(T instance);
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

/// 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.
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer) :
    assert(_constructor != null),
    assert(_initializer != null);

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

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

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

91 92
/// A widget that detects gestures.
///
93
/// Attempts to recognize gestures that correspond to its non-null callbacks.
94
///
95 96 97
/// 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.
///
98 99 100
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
101 102 103 104
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
105 106 107 108 109
/// See <http://flutter.io/gestures/> for additional information.
///
/// 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.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
///
/// ## Sample code
///
/// This example makes a rectangle react to being tapped by setting the
/// `_lights` field:
///
/// ```dart
/// new GestureDetector(
///   onTap: () {
///     setState(() { _lights = true; });
///   },
///   child: new Container(
///     color: Colors.yellow,
///     child: new Text('TURN LIGHTS ON'),
///   ),
/// )
/// ```
127 128 129 130 131
///
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
132
class GestureDetector extends StatelessWidget {
133 134 135 136 137 138 139 140 141 142 143
  /// 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.
144
  GestureDetector({
145 146
    Key key,
    this.child,
147
    this.onTapDown,
148 149
    this.onTapUp,
    this.onTap,
150
    this.onTapCancel,
151
    this.onDoubleTap,
152
    this.onLongPress,
153
    this.onVerticalDragDown,
154 155 156
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
157 158
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
159 160 161
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
162 163
    this.onHorizontalDragCancel,
    this.onPanDown,
164 165
    this.onPanStart,
    this.onPanUpdate,
166
    this.onPanEnd,
167
    this.onPanCancel,
168 169
    this.onScaleStart,
    this.onScaleUpdate,
170
    this.onScaleEnd,
Hixie's avatar
Hixie committed
171
    this.behavior,
172
    this.excludeFromSemantics = false
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
  }) : assert(excludeFromSemantics != null),
       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) {
             throw new FlutterError(
               'Incorrect GestureDetector arguments.\n'
               'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.'
             );
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
             throw new FlutterError(
               'Incorrect GestureDetector arguments.\n'
               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'
             );
           }
         }
         return true;
196
       }()),
197
       super(key: key);
198

199
  /// The widget below this widget in the tree.
200 201
  ///
  /// {@macro flutter.widgets.child}
202
  final Widget child;
203

204 205
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
206 207 208 209
  ///
  /// 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.
210
  final GestureTapDownCallback onTapDown;
211 212 213

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
214 215 216
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
Hixie's avatar
Hixie committed
217
  final GestureTapUpCallback onTapUp;
218 219

  /// A tap has occurred.
220 221 222 223 224 225 226 227
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
228
  final GestureTapCallback onTap;
229

230 231
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
232 233 234
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
235
  final GestureTapCancelCallback onTapCancel;
236 237 238

  /// The user has tapped the screen at the same location twice in quick
  /// succession.
239
  final GestureTapCallback onDoubleTap;
240

241 242
  /// A pointer has remained in contact with the screen at the same location for
  /// a long period of time.
243
  final GestureLongPressCallback onLongPress;
244

245
  /// A pointer has contacted the screen and might begin to move vertically.
246 247 248
  final GestureDragDownCallback onVerticalDragDown;

  /// A pointer has contacted the screen and has begun to move vertically.
249
  final GestureDragStartCallback onVerticalDragStart;
250 251 252

  /// A pointer that is in contact with the screen and moving vertically has
  /// moved in the vertical direction.
253
  final GestureDragUpdateCallback onVerticalDragUpdate;
254 255 256 257

  /// A pointer that was previously in contact with the screen and moving
  /// vertically is no longer in contact with the screen and was moving at a
  /// specific velocity when it stopped contacting the screen.
258 259
  final GestureDragEndCallback onVerticalDragEnd;

260 261
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
262 263
  final GestureDragCancelCallback onVerticalDragCancel;

264
  /// A pointer has contacted the screen and might begin to move horizontally.
265 266 267
  final GestureDragDownCallback onHorizontalDragDown;

  /// A pointer has contacted the screen and has begun to move horizontally.
268
  final GestureDragStartCallback onHorizontalDragStart;
269 270 271

  /// A pointer that is in contact with the screen and moving horizontally has
  /// moved in the horizontal direction.
272
  final GestureDragUpdateCallback onHorizontalDragUpdate;
273 274 275 276

  /// A pointer that was previously in contact with the screen and moving
  /// horizontally is no longer in contact with the screen and was moving at a
  /// specific velocity when it stopped contacting the screen.
277 278
  final GestureDragEndCallback onHorizontalDragEnd;

279 280
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
281 282
  final GestureDragCancelCallback onHorizontalDragCancel;

283
  /// A pointer has contacted the screen and might begin to move.
284
  final GestureDragDownCallback onPanDown;
285 286

  /// A pointer has contacted the screen and has begun to move.
287
  final GestureDragStartCallback onPanStart;
288 289

  /// A pointer that is in contact with the screen and moving has moved again.
290
  final GestureDragUpdateCallback onPanUpdate;
291 292 293 294

  /// A pointer that was previously in contact with the screen and moving
  /// is no longer in contact with the screen and was moving at a specific
  /// velocity when it stopped contacting the screen.
295
  final GestureDragEndCallback onPanEnd;
296 297

  /// The pointer that previously triggered [onPanDown] did not complete.
298
  final GestureDragCancelCallback onPanCancel;
299

300 301
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
302
  final GestureScaleStartCallback onScaleStart;
303 304 305

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
306
  final GestureScaleUpdateCallback onScaleUpdate;
307 308

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

311
  /// How this gesture detector should behave during hit testing.
312 313 314
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
315 316
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
317 318 319 320 321 322 323
  /// 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;

324
  @override
325
  Widget build(BuildContext context) {
326
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
327 328

    if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
329
      gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
330
        () => new TapGestureRecognizer(debugOwner: this),
331 332 333 334 335 336 337 338
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
            ..onTapCancel = onTapCancel;
        },
      );
339
    }
340

341
    if (onDoubleTap != null) {
342
      gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
343
        () => new DoubleTapGestureRecognizer(debugOwner: this),
344 345 346 347 348
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
349
    }
350

351
    if (onLongPress != null) {
352
      gestures[LongPressGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
353
        () => new LongPressGestureRecognizer(debugOwner: this),
354 355 356 357 358
        (LongPressGestureRecognizer instance) {
          instance
            ..onLongPress = onLongPress;
        },
      );
359
    }
360

361 362 363 364 365
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
366
      gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
367
        () => new VerticalDragGestureRecognizer(debugOwner: this),
368 369 370 371 372 373 374 375 376
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
            ..onCancel = onVerticalDragCancel;
        },
      );
377
    }
378

379 380 381 382 383
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
384
      gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
385
        () => new HorizontalDragGestureRecognizer(debugOwner: this),
386 387 388 389 390 391 392 393 394
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
            ..onCancel = onHorizontalDragCancel;
        },
      );
395
    }
396

397 398 399 400 401
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
402
      gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
403
        () => new PanGestureRecognizer(debugOwner: this),
404 405 406 407 408 409 410 411 412
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
            ..onCancel = onPanCancel;
        },
      );
413
    }
414

415
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
416
      gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
417
        () => new ScaleGestureRecognizer(debugOwner: this),
418 419 420 421 422 423 424
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
            ..onEnd = onScaleEnd;
        },
      );
425
    }
426

427 428 429 430
    return new RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
431
      child: child,
432
    );
433
  }
434
}
435

436 437 438 439
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
440
/// [RawGestureDetector] is useful primarily when developing your
441
/// own gesture recognizers.
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
/// ## Sample code
///
/// 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
/// new RawGestureDetector(
///   gestures: <Type, GestureRecognizerFactory>{
///     TapGestureRecognizer: new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => new TapGestureRecognizer(),
///       (TapGestureRecognizer instance) {
///         instance
459 460 461 462
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
463 464 465 466 467 468 469 470 471 472
///       },
///     ),
///   },
///   child: new Container(width: 300.0, height: 300.0, color: Colors.yellow, child: new Text(_last)),
/// )
/// ```
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
473
///  * [Listener], a widget that reports raw pointer events.
474
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
475
class RawGestureDetector extends StatefulWidget {
476 477 478
  /// Creates a widget that detects gestures.
  ///
  /// By default, gesture detectors contribute semantic information to the tree
479 480
  /// that is used by assistive technology. This can be controlled using
  /// [excludeFromSemantics].
481
  const RawGestureDetector({
482 483
    Key key,
    this.child,
484
    this.gestures = const <Type, GestureRecognizerFactory>{},
485
    this.behavior,
486
    this.excludeFromSemantics = false
487 488 489
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
490

491
  /// The widget below this widget in the tree.
492 493
  ///
  /// {@macro flutter.widgets.child}
494 495
  final Widget child;

496
  /// The gestures that this widget will attempt to recognize.
497 498 499 500 501 502
  ///
  /// 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].
503 504 505
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
506 507 508
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
509 510 511 512 513 514 515 516 517
  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;

518
  @override
519 520 521
  RawGestureDetectorState createState() => new RawGestureDetectorState();
}

522
/// State for a [RawGestureDetector].
523 524 525
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

526
  @override
527 528
  void initState() {
    super.initState();
529
    _syncAll(widget.gestures);
530 531
  }

532
  @override
533
  void didUpdateWidget(RawGestureDetector oldWidget) {
534
    super.didUpdateWidget(oldWidget);
535
    _syncAll(widget.gestures);
536 537
  }

538
  /// This method can be called after the build phase, during the
539
  /// layout of the nearest descendant [RenderObjectWidget] of the
540 541 542 543 544 545 546
  /// 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.
547 548 549 550
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
551 552
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
553
      if (!context.findRenderObject().owner.debugDoingLayout) {
554
        throw new FlutterError(
555 556
          'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
          'The replaceGestureRecognizers() method can only be called during the layout phase. '
557 558
          '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 '
559 560 561
          'RawGestureDetector or GestureDetector object.'
        );
      }
562
      return true;
563
    }());
564
    _syncAll(gestures);
565
    if (!widget.excludeFromSemantics) {
566
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
567
      context.visitChildElements((Element element) {
568
        final _GestureSemantics widget = element.widget;
569
        widget._updateHandlers(semanticsGestureHandler);
570
      });
571 572 573
    }
  }

574 575 576 577 578
  /// This method can be called outside of the build phase to filter the list of
  /// available semantic actions.
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
579 580 581 582 583
  ///
  /// 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
584
  /// actions to filter changes, it must be called again.
585 586
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
    assert(() {
587 588
      final Element element = context;
      if (element.owner.debugBuilding) {
589 590
        throw new FlutterError(
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
591
          'The replaceSemanticsActions() method can only be called outside of the build phase.'
592 593 594
        );
      }
      return true;
595
    }());
596 597
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
598
      semanticsGestureHandler.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
599 600 601
    }
  }

602
  @override
603 604 605 606 607
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
608 609
  }

610 611
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
612
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
613 614
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
615 616
      assert(gestures[type] != null);
      assert(gestures[type]._debugAssertTypeMatches(type));
617
      assert(!_recognizers.containsKey(type));
618 619 620
      _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]);
621 622 623 624 625
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
626 627
  }

Ian Hickson's avatar
Ian Hickson committed
628
  void _handlePointerDown(PointerDownEvent event) {
629 630 631
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
632 633
  }

634
  HitTestBehavior get _defaultBehavior {
635
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
636 637
  }

638 639
  void _handleSemanticsTap() {
    final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
640 641
    assert(recognizer != null);
    if (recognizer.onTapDown != null)
642
      recognizer.onTapDown(new TapDownDetails());
643
    if (recognizer.onTapUp != null)
644
      recognizer.onTapUp(new TapUpDetails());
645 646
    if (recognizer.onTap != null)
      recognizer.onTap();
Hixie's avatar
Hixie committed
647 648
  }

649 650
  void _handleSemanticsLongPress() {
    final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
651 652 653
    assert(recognizer != null);
    if (recognizer.onLongPress != null)
      recognizer.onLongPress();
Hixie's avatar
Hixie committed
654 655
  }

656
  void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) {
657
    {
658
      final HorizontalDragGestureRecognizer recognizer = _recognizers[HorizontalDragGestureRecognizer];
659
      if (recognizer != null) {
660 661
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
662
        if (recognizer.onStart != null)
663
          recognizer.onStart(new DragStartDetails());
664
        if (recognizer.onUpdate != null)
665
          recognizer.onUpdate(updateDetails);
666
        if (recognizer.onEnd != null)
667
          recognizer.onEnd(new DragEndDetails(primaryVelocity: 0.0));
668 669 670 671
        return;
      }
    }
    {
672
      final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
673
      if (recognizer != null) {
674 675
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
676
        if (recognizer.onStart != null)
677
          recognizer.onStart(new DragStartDetails());
678
        if (recognizer.onUpdate != null)
679
          recognizer.onUpdate(updateDetails);
680
        if (recognizer.onEnd != null)
681
          recognizer.onEnd(new DragEndDetails());
682 683
        return;
      }
Hixie's avatar
Hixie committed
684 685 686
    }
  }

687
  void _handleSemanticsVerticalDragUpdate(DragUpdateDetails updateDetails) {
688
    {
689
      final VerticalDragGestureRecognizer recognizer = _recognizers[VerticalDragGestureRecognizer];
690
      if (recognizer != null) {
691 692
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
693
        if (recognizer.onStart != null)
694
          recognizer.onStart(new DragStartDetails());
695
        if (recognizer.onUpdate != null)
696
          recognizer.onUpdate(updateDetails);
697
        if (recognizer.onEnd != null)
698
          recognizer.onEnd(new DragEndDetails(primaryVelocity: 0.0));
699 700
        return;
      }
Hixie's avatar
Hixie committed
701
    }
702
    {
703
      final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
704
      if (recognizer != null) {
705 706
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
707
        if (recognizer.onStart != null)
708
          recognizer.onStart(new DragStartDetails());
709
        if (recognizer.onUpdate != null)
710
          recognizer.onUpdate(updateDetails);
711
        if (recognizer.onEnd != null)
712
          recognizer.onEnd(new DragEndDetails());
713 714 715
        return;
      }
    }
Hixie's avatar
Hixie committed
716 717
  }

718
  @override
719 720 721 722 723 724 725 726
  Widget build(BuildContext context) {
    Widget result = new Listener(
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child
    );
    if (!widget.excludeFromSemantics)
      result = new _GestureSemantics(owner: this, child: result);
727 728
    return result;
  }
Hixie's avatar
Hixie committed
729

730
  @override
731 732
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
733
    if (_recognizers == null) {
734
      properties.add(new DiagnosticsNode.message('DISPOSED'));
735 736
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
737 738
      properties.add(new IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
      properties.add(new IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
739
    }
740
    properties.add(new EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
741 742 743 744 745 746 747 748 749 750 751 752 753 754
  }
}

class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
    Key key,
    Widget child,
    this.owner
  }) : super(key: key, child: child);

  final RawGestureDetectorState owner;

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
755 756 757 758 759 760
    return new RenderSemanticsGestureHandler(
      onTap: _onTapHandler,
      onLongPress: _onLongPressHandler,
      onHorizontalDragUpdate: _onHorizontalDragUpdateHandler,
      onVerticalDragUpdate: _onVerticalDragUpdateHandler,
    );
761 762 763
  }

  void _updateHandlers(RenderSemanticsGestureHandler renderObject) {
764
    renderObject
765 766 767 768
      ..onTap = _onTapHandler
      ..onLongPress = _onLongPressHandler
      ..onHorizontalDragUpdate = _onHorizontalDragUpdateHandler
      ..onVerticalDragUpdate = _onVerticalDragUpdateHandler;
Hixie's avatar
Hixie committed
769
  }
770 771 772

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
773
    _updateHandlers(renderObject);
774
  }
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792

  GestureTapCallback get _onTapHandler {
    return owner._recognizers.containsKey(TapGestureRecognizer) ? owner._handleSemanticsTap : null;
  }

  GestureTapCallback get _onLongPressHandler {
    return owner._recognizers.containsKey(LongPressGestureRecognizer) ? owner._handleSemanticsLongPress : null;
  }

  GestureDragUpdateCallback get _onHorizontalDragUpdateHandler {
    return owner._recognizers.containsKey(HorizontalDragGestureRecognizer) ||
        owner._recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsHorizontalDragUpdate : null;
  }

  GestureDragUpdateCallback get _onVerticalDragUpdateHandler {
    return owner._recognizers.containsKey(VerticalDragGestureRecognizer) ||
        owner._recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsVerticalDragUpdate : null;
  }
Hixie's avatar
Hixie committed
793
}