gesture_detector.dart 28 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.
///
Hixie's avatar
Hixie committed
98 99 100 101
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
102 103 104 105 106
/// 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.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
///
/// ## 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'),
///   ),
/// )
/// ```
124
class GestureDetector extends StatelessWidget {
125 126 127 128 129 130 131 132 133 134 135
  /// 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.
136
  GestureDetector({
137 138
    Key key,
    this.child,
139
    this.onTapDown,
140 141
    this.onTapUp,
    this.onTap,
142
    this.onTapCancel,
143
    this.onDoubleTap,
144
    this.onLongPress,
145
    this.onVerticalDragDown,
146 147 148
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
149 150
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
151 152 153
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
154 155
    this.onHorizontalDragCancel,
    this.onPanDown,
156 157
    this.onPanStart,
    this.onPanUpdate,
158
    this.onPanEnd,
159
    this.onPanCancel,
160 161
    this.onScaleStart,
    this.onScaleUpdate,
162
    this.onScaleEnd,
Hixie's avatar
Hixie committed
163 164
    this.behavior,
    this.excludeFromSemantics: false
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
  }) : 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;
188
       }()),
189
       super(key: key);
190

191
  /// The widget below this widget in the tree.
192
  final Widget child;
193

194 195
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
196
  final GestureTapDownCallback onTapDown;
197 198 199

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

  /// A tap has occurred.
203
  final GestureTapCallback onTap;
204

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

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

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

217
  /// A pointer has contacted the screen and might begin to move vertically.
218 219 220
  final GestureDragDownCallback onVerticalDragDown;

  /// A pointer has contacted the screen and has begun to move vertically.
221
  final GestureDragStartCallback onVerticalDragStart;
222 223 224

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

  /// 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.
230 231
  final GestureDragEndCallback onVerticalDragEnd;

232 233
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
234 235
  final GestureDragCancelCallback onVerticalDragCancel;

236
  /// A pointer has contacted the screen and might begin to move horizontally.
237 238 239
  final GestureDragDownCallback onHorizontalDragDown;

  /// A pointer has contacted the screen and has begun to move horizontally.
240
  final GestureDragStartCallback onHorizontalDragStart;
241 242 243

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

  /// 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.
249 250
  final GestureDragEndCallback onHorizontalDragEnd;

251 252
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
253 254
  final GestureDragCancelCallback onHorizontalDragCancel;

255
  /// A pointer has contacted the screen and might begin to move.
256
  final GestureDragDownCallback onPanDown;
257 258

  /// A pointer has contacted the screen and has begun to move.
259
  final GestureDragStartCallback onPanStart;
260 261

  /// A pointer that is in contact with the screen and moving has moved again.
262
  final GestureDragUpdateCallback onPanUpdate;
263 264 265 266

  /// 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.
267
  final GestureDragEndCallback onPanEnd;
268 269

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

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

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
278
  final GestureScaleUpdateCallback onScaleUpdate;
279 280

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

283
  /// How this gesture detector should behave during hit testing.
284 285
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
286 287 288 289 290 291 292
  /// 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;

293
  @override
294
  Widget build(BuildContext context) {
295
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
296 297

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

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

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

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

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

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

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

396 397 398 399
    return new RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
400
      child: child,
401
    );
402
  }
403
}
404

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

460
  /// The widget below this widget in the tree.
461 462
  final Widget child;

463
  /// The gestures that this widget will attempt to recognize.
464 465 466 467 468 469
  ///
  /// 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].
470 471 472 473 474 475 476 477 478 479 480 481
  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;

482
  @override
483 484 485
  RawGestureDetectorState createState() => new RawGestureDetectorState();
}

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

490
  @override
491 492
  void initState() {
    super.initState();
493
    _syncAll(widget.gestures);
494 495
  }

496
  @override
497
  void didUpdateWidget(RawGestureDetector oldWidget) {
498
    super.didUpdateWidget(oldWidget);
499
    _syncAll(widget.gestures);
500 501
  }

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

538 539 540 541 542 543 544 545 546 547
  /// This method can be called after the build phase, during the layout of the
  /// nearest descendant [RenderObjectWidget] of the gesture detector, to filter
  /// the list of available semantic actions.
  ///
  /// 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
  /// actions to filter changes, it must be called again (during the layout of
  /// the nearest descendant [RenderObjectWidget] of the gesture detector).
548 549 550 551 552 553 554 555 556
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
    assert(() {
      if (!context.findRenderObject().owner.debugDoingLayout) {
        throw new FlutterError(
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
          'The replaceSemanticsActions() method can only be called during the layout phase.'
        );
      }
      return true;
557
    }());
558 559
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
560 561 562 563 564 565 566 567 568 569
      semanticsGestureHandler.validActions = actions;
    }
  }

  /// 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.
  ///
570 571
  /// The event will not be sent if [RawGestureDetector.excludeFromSemantics] is
  /// set to true.
572 573 574 575
  void sendSemanticsEvent(SemanticsEvent event) {
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
      semanticsGestureHandler.sendSemanticsEvent(event);
576 577 578
    }
  }

579
  @override
580 581 582 583 584
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
585 586
  }

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

Ian Hickson's avatar
Ian Hickson committed
605
  void _handlePointerDown(PointerDownEvent event) {
606 607 608
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
609 610
  }

611
  HitTestBehavior get _defaultBehavior {
612
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
613 614
  }

615 616
  void _handleSemanticsTap() {
    final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
617 618
    assert(recognizer != null);
    if (recognizer.onTapDown != null)
619
      recognizer.onTapDown(new TapDownDetails());
620
    if (recognizer.onTapUp != null)
621
      recognizer.onTapUp(new TapUpDetails());
622 623
    if (recognizer.onTap != null)
      recognizer.onTap();
Hixie's avatar
Hixie committed
624 625
  }

626 627
  void _handleSemanticsLongPress() {
    final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
628 629 630
    assert(recognizer != null);
    if (recognizer.onLongPress != null)
      recognizer.onLongPress();
Hixie's avatar
Hixie committed
631 632
  }

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

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

695
  @override
696 697 698 699 700 701 702 703
  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);
704 705
    return result;
  }
Hixie's avatar
Hixie committed
706

707 708 709 710 711 712 713
  @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();
714
      description.add(new IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
715
      description.add(new IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
    }
    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;
739
    renderObject
740 741
      ..onTap = recognizers.containsKey(TapGestureRecognizer) ? owner._handleSemanticsTap : null
      ..onLongPress = recognizers.containsKey(LongPressGestureRecognizer) ? owner._handleSemanticsLongPress : null
742
      ..onHorizontalDragUpdate = recognizers.containsKey(HorizontalDragGestureRecognizer) ||
743
          recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsHorizontalDragUpdate : null
744
      ..onVerticalDragUpdate = recognizers.containsKey(VerticalDragGestureRecognizer) ||
745
          recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsVerticalDragUpdate : null;
Hixie's avatar
Hixie committed
746
  }
747 748 749

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
750
    _updateHandlers(renderObject);
751
  }
Hixie's avatar
Hixie committed
752
}