gesture_detector.dart 28.5 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 172
    this.behavior,
    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
  final GestureTapDownCallback onTapDown;
207 208 209

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
Hixie's avatar
Hixie committed
210
  final GestureTapUpCallback onTapUp;
211 212

  /// A tap has occurred.
213
  final GestureTapCallback onTap;
214

215 216
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
217
  final GestureTapCancelCallback onTapCancel;
218 219 220

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

223 224
  /// A pointer has remained in contact with the screen at the same location for
  /// a long period of time.
225
  final GestureLongPressCallback onLongPress;
226

227
  /// A pointer has contacted the screen and might begin to move vertically.
228 229 230
  final GestureDragDownCallback onVerticalDragDown;

  /// A pointer has contacted the screen and has begun to move vertically.
231
  final GestureDragStartCallback onVerticalDragStart;
232 233 234

  /// A pointer that is in contact with the screen and moving vertically has
  /// moved in the vertical direction.
235
  final GestureDragUpdateCallback onVerticalDragUpdate;
236 237 238 239

  /// 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.
240 241
  final GestureDragEndCallback onVerticalDragEnd;

242 243
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
244 245
  final GestureDragCancelCallback onVerticalDragCancel;

246
  /// A pointer has contacted the screen and might begin to move horizontally.
247 248 249
  final GestureDragDownCallback onHorizontalDragDown;

  /// A pointer has contacted the screen and has begun to move horizontally.
250
  final GestureDragStartCallback onHorizontalDragStart;
251 252 253

  /// A pointer that is in contact with the screen and moving horizontally has
  /// moved in the horizontal direction.
254
  final GestureDragUpdateCallback onHorizontalDragUpdate;
255 256 257 258

  /// 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.
259 260
  final GestureDragEndCallback onHorizontalDragEnd;

261 262
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
263 264
  final GestureDragCancelCallback onHorizontalDragCancel;

265
  /// A pointer has contacted the screen and might begin to move.
266
  final GestureDragDownCallback onPanDown;
267 268

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

  /// A pointer that is in contact with the screen and moving has moved again.
272
  final GestureDragUpdateCallback onPanUpdate;
273 274 275 276

  /// 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.
277
  final GestureDragEndCallback onPanEnd;
278 279

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

282 283
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
284
  final GestureScaleStartCallback onScaleStart;
285 286 287

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
288
  final GestureScaleUpdateCallback onScaleUpdate;
289 290

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

293
  /// How this gesture detector should behave during hit testing.
294 295 296
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
297 298
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
299 300 301 302 303 304 305
  /// 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;

306
  @override
307
  Widget build(BuildContext context) {
308
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
309 310

    if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
311
      gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
312
        () => new TapGestureRecognizer(debugOwner: this),
313 314 315 316 317 318 319 320
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
            ..onTapCancel = onTapCancel;
        },
      );
321
    }
322

323
    if (onDoubleTap != null) {
324
      gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
325
        () => new DoubleTapGestureRecognizer(debugOwner: this),
326 327 328 329 330
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
331
    }
332

333
    if (onLongPress != null) {
334
      gestures[LongPressGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
335
        () => new LongPressGestureRecognizer(debugOwner: this),
336 337 338 339 340
        (LongPressGestureRecognizer instance) {
          instance
            ..onLongPress = onLongPress;
        },
      );
341
    }
342

343 344 345 346 347
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
348
      gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
349
        () => new VerticalDragGestureRecognizer(debugOwner: this),
350 351 352 353 354 355 356 357 358
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
            ..onCancel = onVerticalDragCancel;
        },
      );
359
    }
360

361 362 363 364 365
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
366
      gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
367
        () => new HorizontalDragGestureRecognizer(debugOwner: this),
368 369 370 371 372 373 374 375 376
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
            ..onCancel = onHorizontalDragCancel;
        },
      );
377
    }
378

379 380 381 382 383
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
384
      gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
385
        () => new PanGestureRecognizer(debugOwner: this),
386 387 388 389 390 391 392 393 394
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
            ..onCancel = onPanCancel;
        },
      );
395
    }
396

397
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
398
      gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
399
        () => new ScaleGestureRecognizer(debugOwner: this),
400 401 402 403 404 405 406
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
            ..onEnd = onScaleEnd;
        },
      );
407
    }
408

409 410 411 412
    return new RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
413
      child: child,
414
    );
415
  }
416
}
417

418 419 420 421
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
422
/// [RawGestureDetector] is useful primarily when developing your
423
/// own gesture recognizers.
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
///
/// 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
441 442 443 444
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
445 446 447 448 449 450 451 452 453 454
///       },
///     ),
///   },
///   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.
455
///  * [Listener], a widget that reports raw pointer events.
456
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
457
class RawGestureDetector extends StatefulWidget {
458 459 460
  /// Creates a widget that detects gestures.
  ///
  /// By default, gesture detectors contribute semantic information to the tree
461 462
  /// that is used by assistive technology. This can be controlled using
  /// [excludeFromSemantics].
463
  const RawGestureDetector({
464 465 466 467 468
    Key key,
    this.child,
    this.gestures: const <Type, GestureRecognizerFactory>{},
    this.behavior,
    this.excludeFromSemantics: false
469 470 471
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
472

473
  /// The widget below this widget in the tree.
474 475
  ///
  /// {@macro flutter.widgets.child}
476 477
  final Widget child;

478
  /// The gestures that this widget will attempt to recognize.
479 480 481 482 483 484
  ///
  /// 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].
485 486 487
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
488 489 490
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
491 492 493 494 495 496 497 498 499
  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;

500
  @override
501 502 503
  RawGestureDetectorState createState() => new RawGestureDetectorState();
}

504
/// State for a [RawGestureDetector].
505 506 507
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

508
  @override
509 510
  void initState() {
    super.initState();
511
    _syncAll(widget.gestures);
512 513
  }

514
  @override
515
  void didUpdateWidget(RawGestureDetector oldWidget) {
516
    super.didUpdateWidget(oldWidget);
517
    _syncAll(widget.gestures);
518 519
  }

520
  /// This method can be called after the build phase, during the
521
  /// layout of the nearest descendant [RenderObjectWidget] of the
522 523 524 525 526 527 528
  /// 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.
529 530 531 532
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
533 534
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
535
      if (!context.findRenderObject().owner.debugDoingLayout) {
536
        throw new FlutterError(
537 538
          'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
          'The replaceGestureRecognizers() method can only be called during the layout phase. '
539 540
          '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 '
541 542 543
          'RawGestureDetector or GestureDetector object.'
        );
      }
544
      return true;
545
    }());
546
    _syncAll(gestures);
547
    if (!widget.excludeFromSemantics) {
548
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
549
      context.visitChildElements((Element element) {
550
        final _GestureSemantics widget = element.widget;
551
        widget._updateHandlers(semanticsGestureHandler);
552
      });
553 554 555
    }
  }

556 557 558 559 560
  /// 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.
561 562 563 564 565
  ///
  /// 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
566
  /// actions to filter changes, it must be called again.
567 568
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
    assert(() {
569 570
      final Element element = context;
      if (element.owner.debugBuilding) {
571 572
        throw new FlutterError(
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
573
          'The replaceSemanticsActions() method can only be called outside of the build phase.'
574 575 576
        );
      }
      return true;
577
    }());
578 579
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
580
      semanticsGestureHandler.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
581 582 583
    }
  }

584
  @override
585 586 587 588 589
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
590 591
  }

592 593
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
594
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
595 596
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
597 598
      assert(gestures[type] != null);
      assert(gestures[type]._debugAssertTypeMatches(type));
599
      assert(!_recognizers.containsKey(type));
600 601 602
      _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]);
603 604 605 606 607
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
608 609
  }

Ian Hickson's avatar
Ian Hickson committed
610
  void _handlePointerDown(PointerDownEvent event) {
611 612 613
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
614 615
  }

616
  HitTestBehavior get _defaultBehavior {
617
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
618 619
  }

620 621
  void _handleSemanticsTap() {
    final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
622 623
    assert(recognizer != null);
    if (recognizer.onTapDown != null)
624
      recognizer.onTapDown(new TapDownDetails());
625
    if (recognizer.onTapUp != null)
626
      recognizer.onTapUp(new TapUpDetails());
627 628
    if (recognizer.onTap != null)
      recognizer.onTap();
Hixie's avatar
Hixie committed
629 630
  }

631 632
  void _handleSemanticsLongPress() {
    final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
633 634 635
    assert(recognizer != null);
    if (recognizer.onLongPress != null)
      recognizer.onLongPress();
Hixie's avatar
Hixie committed
636 637
  }

638
  void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) {
639
    {
640
      final HorizontalDragGestureRecognizer recognizer = _recognizers[HorizontalDragGestureRecognizer];
641
      if (recognizer != null) {
642 643
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
644
        if (recognizer.onStart != null)
645
          recognizer.onStart(new DragStartDetails());
646
        if (recognizer.onUpdate != null)
647
          recognizer.onUpdate(updateDetails);
648
        if (recognizer.onEnd != null)
649
          recognizer.onEnd(new DragEndDetails(primaryVelocity: 0.0));
650 651 652 653
        return;
      }
    }
    {
654
      final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
655
      if (recognizer != null) {
656 657
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
658
        if (recognizer.onStart != null)
659
          recognizer.onStart(new DragStartDetails());
660
        if (recognizer.onUpdate != null)
661
          recognizer.onUpdate(updateDetails);
662
        if (recognizer.onEnd != null)
663
          recognizer.onEnd(new DragEndDetails());
664 665
        return;
      }
Hixie's avatar
Hixie committed
666 667 668
    }
  }

669
  void _handleSemanticsVerticalDragUpdate(DragUpdateDetails updateDetails) {
670
    {
671
      final VerticalDragGestureRecognizer recognizer = _recognizers[VerticalDragGestureRecognizer];
672
      if (recognizer != null) {
673 674
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
675
        if (recognizer.onStart != null)
676
          recognizer.onStart(new DragStartDetails());
677
        if (recognizer.onUpdate != null)
678
          recognizer.onUpdate(updateDetails);
679
        if (recognizer.onEnd != null)
680
          recognizer.onEnd(new DragEndDetails(primaryVelocity: 0.0));
681 682
        return;
      }
Hixie's avatar
Hixie committed
683
    }
684
    {
685
      final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
686
      if (recognizer != null) {
687 688
        if (recognizer.onDown != null)
          recognizer.onDown(new DragDownDetails());
689
        if (recognizer.onStart != null)
690
          recognizer.onStart(new DragStartDetails());
691
        if (recognizer.onUpdate != null)
692
          recognizer.onUpdate(updateDetails);
693
        if (recognizer.onEnd != null)
694
          recognizer.onEnd(new DragEndDetails());
695 696 697
        return;
      }
    }
Hixie's avatar
Hixie committed
698 699
  }

700
  @override
701 702 703 704 705 706 707 708
  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);
709 710
    return result;
  }
Hixie's avatar
Hixie committed
711

712
  @override
713 714
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
715
    if (_recognizers == null) {
716
      properties.add(new DiagnosticsNode.message('DISPOSED'));
717 718
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
719 720
      properties.add(new IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
      properties.add(new IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
721
    }
722
    properties.add(new EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
723 724 725 726 727 728 729 730 731 732 733 734 735 736
  }
}

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) {
737 738 739 740 741 742
    return new RenderSemanticsGestureHandler(
      onTap: _onTapHandler,
      onLongPress: _onLongPressHandler,
      onHorizontalDragUpdate: _onHorizontalDragUpdateHandler,
      onVerticalDragUpdate: _onVerticalDragUpdateHandler,
    );
743 744 745
  }

  void _updateHandlers(RenderSemanticsGestureHandler renderObject) {
746
    renderObject
747 748 749 750
      ..onTap = _onTapHandler
      ..onLongPress = _onLongPressHandler
      ..onHorizontalDragUpdate = _onHorizontalDragUpdateHandler
      ..onVerticalDragUpdate = _onVerticalDragUpdateHandler;
Hixie's avatar
Hixie committed
751
  }
752 753 754

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
755
    _updateHandlers(renderObject);
756
  }
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774

  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
775
}