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.
///
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 196
  ///
  /// {@macro flutter.widgets.child}
197
  final Widget child;
198

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

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

  /// A tap has occurred.
208
  final GestureTapCallback onTap;
209

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

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

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

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

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

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

  /// 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.
235 236
  final GestureDragEndCallback onVerticalDragEnd;

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

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

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

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

  /// 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.
254 255
  final GestureDragEndCallback onHorizontalDragEnd;

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

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

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

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

  /// 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.
272
  final GestureDragEndCallback onPanEnd;
273 274

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

465
  /// The widget below this widget in the tree.
466 467
  ///
  /// {@macro flutter.widgets.child}
468 469
  final Widget child;

470
  /// The gestures that this widget will attempt to recognize.
471 472 473 474 475 476
  ///
  /// 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].
477 478 479 480 481 482 483 484 485 486 487 488
  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;

489
  @override
490 491 492
  RawGestureDetectorState createState() => new RawGestureDetectorState();
}

493
/// State for a [RawGestureDetector].
494 495 496
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

497
  @override
498 499
  void initState() {
    super.initState();
500
    _syncAll(widget.gestures);
501 502
  }

503
  @override
504
  void didUpdateWidget(RawGestureDetector oldWidget) {
505
    super.didUpdateWidget(oldWidget);
506
    _syncAll(widget.gestures);
507 508
  }

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

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

573
  @override
574 575 576 577 578
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
579 580
  }

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

Ian Hickson's avatar
Ian Hickson committed
599
  void _handlePointerDown(PointerDownEvent event) {
600 601 602
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
603 604
  }

605
  HitTestBehavior get _defaultBehavior {
606
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
607 608
  }

609 610
  void _handleSemanticsTap() {
    final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
611 612
    assert(recognizer != null);
    if (recognizer.onTapDown != null)
613
      recognizer.onTapDown(new TapDownDetails());
614
    if (recognizer.onTapUp != null)
615
      recognizer.onTapUp(new TapUpDetails());
616 617
    if (recognizer.onTap != null)
      recognizer.onTap();
Hixie's avatar
Hixie committed
618 619
  }

620 621
  void _handleSemanticsLongPress() {
    final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
622 623 624
    assert(recognizer != null);
    if (recognizer.onLongPress != null)
      recognizer.onLongPress();
Hixie's avatar
Hixie committed
625 626
  }

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

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

689
  @override
690 691 692 693 694 695 696 697
  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);
698 699
    return result;
  }
Hixie's avatar
Hixie committed
700

701
  @override
702 703
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
704
    if (_recognizers == null) {
705
      properties.add(new DiagnosticsNode.message('DISPOSED'));
706 707
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
708 709
      properties.add(new IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
      properties.add(new IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
710
    }
711
    properties.add(new EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
712 713 714 715 716 717 718 719 720 721 722 723 724 725
  }
}

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) {
726 727 728 729 730 731
    return new RenderSemanticsGestureHandler(
      onTap: _onTapHandler,
      onLongPress: _onLongPressHandler,
      onHorizontalDragUpdate: _onHorizontalDragUpdateHandler,
      onVerticalDragUpdate: _onVerticalDragUpdateHandler,
    );
732 733 734
  }

  void _updateHandlers(RenderSemanticsGestureHandler renderObject) {
735
    renderObject
736 737 738 739
      ..onTap = _onTapHandler
      ..onLongPress = _onLongPressHandler
      ..onHorizontalDragUpdate = _onHorizontalDragUpdateHandler
      ..onVerticalDragUpdate = _onVerticalDragUpdateHandler;
Hixie's avatar
Hixie committed
740
  }
741 742 743

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
744
    _updateHandlers(renderObject);
745
  }
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763

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