gesture_detector.dart 28.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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
@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].
typedef T GestureRecognizerFactoryConstructor<T extends GestureRecognizer>();

/// Signature for closures that implement [GestureRecognizerFactory.initializer].
typedef void GestureRecognizerFactoryInitializer<T extends GestureRecognizer>(T instance);

/// 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
class GestureDetector extends StatelessWidget {
128 129 130 131 132 133 134 135 136 137 138
  /// 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.
139
  GestureDetector({
140 141
    Key key,
    this.child,
142
    this.onTapDown,
143 144
    this.onTapUp,
    this.onTap,
145
    this.onTapCancel,
146
    this.onDoubleTap,
147
    this.onLongPress,
148
    this.onVerticalDragDown,
149 150 151
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
152 153
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
154 155 156
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
157 158
    this.onHorizontalDragCancel,
    this.onPanDown,
159 160
    this.onPanStart,
    this.onPanUpdate,
161
    this.onPanEnd,
162
    this.onPanCancel,
163 164
    this.onScaleStart,
    this.onScaleUpdate,
165
    this.onScaleEnd,
Hixie's avatar
Hixie committed
166 167
    this.behavior,
    this.excludeFromSemantics: false
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
  }) : 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;
191
       }()),
192
       super(key: key);
193

194
  /// The widget below this widget in the tree.
195
  final Widget child;
196

197 198
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
199
  final GestureTapDownCallback onTapDown;
200 201 202

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

  /// A tap has occurred.
206
  final GestureTapCallback onTap;
207

208 209
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
210
  final GestureTapCancelCallback onTapCancel;
211 212 213

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

216 217
  /// A pointer has remained in contact with the screen at the same location for
  /// a long period of time.
218
  final GestureLongPressCallback onLongPress;
219

220
  /// A pointer has contacted the screen and might begin to move vertically.
221 222 223
  final GestureDragDownCallback onVerticalDragDown;

  /// A pointer has contacted the screen and has begun to move vertically.
224
  final GestureDragStartCallback onVerticalDragStart;
225 226 227

  /// A pointer that is in contact with the screen and moving vertically has
  /// moved in the vertical direction.
228
  final GestureDragUpdateCallback onVerticalDragUpdate;
229 230 231 232

  /// 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.
233 234
  final GestureDragEndCallback onVerticalDragEnd;

235 236
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
237 238
  final GestureDragCancelCallback onVerticalDragCancel;

239
  /// A pointer has contacted the screen and might begin to move horizontally.
240 241 242
  final GestureDragDownCallback onHorizontalDragDown;

  /// A pointer has contacted the screen and has begun to move horizontally.
243
  final GestureDragStartCallback onHorizontalDragStart;
244 245 246

  /// A pointer that is in contact with the screen and moving horizontally has
  /// moved in the horizontal direction.
247
  final GestureDragUpdateCallback onHorizontalDragUpdate;
248 249 250 251

  /// 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.
252 253
  final GestureDragEndCallback onHorizontalDragEnd;

254 255
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
256 257
  final GestureDragCancelCallback onHorizontalDragCancel;

258
  /// A pointer has contacted the screen and might begin to move.
259
  final GestureDragDownCallback onPanDown;
260 261

  /// A pointer has contacted the screen and has begun to move.
262
  final GestureDragStartCallback onPanStart;
263 264

  /// A pointer that is in contact with the screen and moving has moved again.
265
  final GestureDragUpdateCallback onPanUpdate;
266 267 268 269

  /// 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.
270
  final GestureDragEndCallback onPanEnd;
271 272

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

275 276
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
277
  final GestureScaleStartCallback onScaleStart;
278 279 280

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
281
  final GestureScaleUpdateCallback onScaleUpdate;
282 283

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

286
  /// How this gesture detector should behave during hit testing.
287 288
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
289 290 291 292 293 294 295
  /// 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;

296
  @override
297
  Widget build(BuildContext context) {
298
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
299 300

    if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
301
      gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
302
        () => new TapGestureRecognizer(debugOwner: this),
303 304 305 306 307 308 309 310
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
            ..onTapCancel = onTapCancel;
        },
      );
311
    }
312

313
    if (onDoubleTap != null) {
314
      gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
315
        () => new DoubleTapGestureRecognizer(debugOwner: this),
316 317 318 319 320
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
321
    }
322

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

333 334 335 336 337
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
338
      gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
339
        () => new VerticalDragGestureRecognizer(debugOwner: this),
340 341 342 343 344 345 346 347 348
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
            ..onCancel = onVerticalDragCancel;
        },
      );
349
    }
350

351 352 353 354 355
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
356
      gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
357
        () => new HorizontalDragGestureRecognizer(debugOwner: this),
358 359 360 361 362 363 364 365 366
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
            ..onCancel = onHorizontalDragCancel;
        },
      );
367
    }
368

369 370 371 372 373
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
374
      gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
375
        () => new PanGestureRecognizer(debugOwner: this),
376 377 378 379 380 381 382 383 384
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
            ..onCancel = onPanCancel;
        },
      );
385
    }
386

387
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
388
      gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
389
        () => new ScaleGestureRecognizer(debugOwner: this),
390 391 392 393 394 395 396
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
            ..onEnd = onScaleEnd;
        },
      );
397
    }
398

399 400 401 402
    return new RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
403
      child: child,
404
    );
405
  }
406
}
407

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

463
  /// The widget below this widget in the tree.
464 465
  final Widget child;

466
  /// The gestures that this widget will attempt to recognize.
467 468 469 470 471 472
  ///
  /// 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].
473 474 475 476 477 478 479 480 481 482 483 484
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
  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;

485
  @override
486 487 488
  RawGestureDetectorState createState() => new RawGestureDetectorState();
}

489
/// State for a [RawGestureDetector].
490 491 492
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

493
  @override
494 495
  void initState() {
    super.initState();
496
    _syncAll(widget.gestures);
497 498
  }

499
  @override
500
  void didUpdateWidget(RawGestureDetector oldWidget) {
501
    super.didUpdateWidget(oldWidget);
502
    _syncAll(widget.gestures);
503 504
  }

505
  /// This method can be called after the build phase, during the
506
  /// layout of the nearest descendant [RenderObjectWidget] of the
507 508 509 510 511 512 513
  /// 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.
514 515 516 517
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
518 519
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
520
      if (!context.findRenderObject().owner.debugDoingLayout) {
521
        throw new FlutterError(
522 523
          'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
          'The replaceGestureRecognizers() method can only be called during the layout phase. '
524 525
          '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 '
526 527 528
          'RawGestureDetector or GestureDetector object.'
        );
      }
529
      return true;
530
    }());
531
    _syncAll(gestures);
532
    if (!widget.excludeFromSemantics) {
533
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
534
      context.visitChildElements((Element element) {
535
        final _GestureSemantics widget = element.widget;
536
        widget._updateHandlers(semanticsGestureHandler);
537
      });
538 539 540
    }
  }

541 542 543 544 545
  /// 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.
546 547 548 549 550
  ///
  /// 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
551
  /// actions to filter changes, it must be called again.
552 553
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
    assert(() {
554 555
      final Element element = context;
      if (element.owner.debugBuilding) {
556 557
        throw new FlutterError(
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
558
          'The replaceSemanticsActions() method can only be called outside of the build phase.'
559 560 561
        );
      }
      return true;
562
    }());
563 564
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
565
      semanticsGestureHandler.validActions = actions;  // will call _markNeedsSemanticsUpdate(), if required.
566 567 568 569 570 571 572 573 574
    }
  }

  /// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is
  /// annotated with this object's semantics information.
  ///
  /// The event can be interpreted by assistive technologies to provide
  /// additional feedback to the user about the state of the UI.
  ///
575 576
  /// The event will not be sent if [RawGestureDetector.excludeFromSemantics] is
  /// set to true.
577 578 579 580
  void sendSemanticsEvent(SemanticsEvent event) {
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
      semanticsGestureHandler.sendSemanticsEvent(event);
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 713 714 715 716 717 718
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    if (_recognizers == null) {
      description.add(new DiagnosticsNode.message('DISPOSED'));
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
719
      description.add(new IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
720
      description.add(new IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
    }
    description.add(new EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
  }
}

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) {
    final RenderSemanticsGestureHandler renderObject = new RenderSemanticsGestureHandler();
    _updateHandlers(renderObject);
    return renderObject;
  }

  void _updateHandlers(RenderSemanticsGestureHandler renderObject) {
    final Map<Type, GestureRecognizer> recognizers = owner._recognizers;
744
    renderObject
745 746
      ..onTap = recognizers.containsKey(TapGestureRecognizer) ? owner._handleSemanticsTap : null
      ..onLongPress = recognizers.containsKey(LongPressGestureRecognizer) ? owner._handleSemanticsLongPress : null
747
      ..onHorizontalDragUpdate = recognizers.containsKey(HorizontalDragGestureRecognizer) ||
748
          recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsHorizontalDragUpdate : null
749
      ..onVerticalDragUpdate = recognizers.containsKey(VerticalDragGestureRecognizer) ||
750
          recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsVerticalDragUpdate : null;
Hixie's avatar
Hixie committed
751
  }
752 753 754

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
755
    _updateHandlers(renderObject);
756
  }
Hixie's avatar
Hixie committed
757
}