binding.dart 6.39 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
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 6
import 'dart:async';
import 'dart:collection';
7
import 'dart:ui' as ui show window, PointerDataPacket;
Ian Hickson's avatar
Ian Hickson committed
8

9
import 'package:flutter/foundation.dart';
Ian Hickson's avatar
Ian Hickson committed
10 11 12

import 'arena.dart';
import 'converter.dart';
13
import 'debug.dart';
Ian Hickson's avatar
Ian Hickson committed
14 15 16 17
import 'events.dart';
import 'hit_test.dart';
import 'pointer_router.dart';

18
/// A binding for the gesture subsystem.
19 20 21 22
abstract class GestureBinding extends BindingBase with HitTestable, HitTestDispatcher, HitTestTarget {
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory GestureBinding._() => null;
Ian Hickson's avatar
Ian Hickson committed
23

24
  @override
Ian Hickson's avatar
Ian Hickson committed
25 26 27
  void initInstances() {
    super.initInstances();
    _instance = this;
28
    ui.window.onPointerDataPacket = _handlePointerDataPacket;
Ian Hickson's avatar
Ian Hickson committed
29 30
  }

31 32 33 34 35 36
  @override
  void unlocked() {
    super.unlocked();
    _flushPointerEventQueue();
  }

37
  /// The singleton instance of this object.
38 39
  static GestureBinding get instance => _instance;
  static GestureBinding _instance;
Ian Hickson's avatar
Ian Hickson committed
40

41 42
  final Queue<PointerEvent> _pendingPointerEvents = new Queue<PointerEvent>();

43
  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
44 45
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
46
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, ui.window.devicePixelRatio));
47 48
    if (!locked)
      _flushPointerEventQueue();
49 50 51 52 53 54 55
  }

  /// Dispatch a [PointerCancelEvent] for the given pointer soon.
  ///
  /// The pointer event will be dispatch before the next pointer event and
  /// before the end of the microtask but not within this function call.
  void cancelPointer(int pointer) {
56
    if (_pendingPointerEvents.isEmpty && !locked)
57 58
      scheduleMicrotask(_flushPointerEventQueue);
    _pendingPointerEvents.addFirst(new PointerCancelEvent(pointer: pointer));
Ian Hickson's avatar
Ian Hickson committed
59 60
  }

61 62 63 64 65 66
  void _flushPointerEventQueue() {
    assert(!locked);
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
  }

Ian Hickson's avatar
Ian Hickson committed
67 68 69 70 71
  /// A router that routes all pointer events received from the engine.
  final PointerRouter pointerRouter = new PointerRouter();

  /// The gesture arenas used for disambiguating the meaning of sequences of
  /// pointer events.
72
  final GestureArenaManager gestureArena = new GestureArenaManager();
Ian Hickson's avatar
Ian Hickson committed
73 74 75 76 77

  /// State for all pointers which are currently down.
  ///
  /// The state of hovering pointers is not tracked because that would require
  /// hit-testing on every frame.
78
  final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
Ian Hickson's avatar
Ian Hickson committed
79 80

  void _handlePointerEvent(PointerEvent event) {
81
    assert(!locked);
82
    HitTestResult result;
Ian Hickson's avatar
Ian Hickson committed
83 84
    if (event is PointerDownEvent) {
      assert(!_hitTests.containsKey(event.pointer));
85
      result = new HitTestResult();
Ian Hickson's avatar
Ian Hickson committed
86 87
      hitTest(result, event.position);
      _hitTests[event.pointer] = result;
88 89 90 91 92
      assert(() {
        if (debugPrintHitTestResults)
          debugPrint('$event: $result');
        return true;
      });
93 94 95 96 97 98
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      result = _hitTests.remove(event.pointer);
    } else if (event.down) {
      result = _hitTests[event.pointer];
    } else {
      return;  // We currently ignore add, remove, and hover move events.
Ian Hickson's avatar
Ian Hickson committed
99
    }
100 101
    if (result != null)
      dispatchEvent(event, result);
Ian Hickson's avatar
Ian Hickson committed
102 103 104
  }

  /// Determine which [HitTestTarget] objects are located at a given position.
105
  @override // from HitTestable
106
  void hitTest(HitTestResult result, Offset position) {
Ian Hickson's avatar
Ian Hickson committed
107 108 109
    result.add(new HitTestEntry(this));
  }

110 111 112 113 114 115
  /// Dispatch an event to a hit test result's path.
  ///
  /// This sends the given event to every [HitTestTarget] in the entries
  /// of the given [HitTestResult], and catches exceptions that any of
  /// the handlers might throw. The `result` argument must not be null.
  @override // from HitTestDispatcher
Ian Hickson's avatar
Ian Hickson committed
116
  void dispatchEvent(PointerEvent event, HitTestResult result) {
117
    assert(!locked);
Ian Hickson's avatar
Ian Hickson committed
118
    assert(result != null);
119 120 121 122
    for (HitTestEntry entry in result.path) {
      try {
        entry.target.handleEvent(event, entry);
      } catch (exception, stack) {
123 124 125 126 127 128 129 130 131 132 133 134 135 136
        FlutterError.reportError(new FlutterErrorDetailsForPointerEventDispatcher(
          exception: exception,
          stack: stack,
          library: 'gesture library',
          context: 'while dispatching a pointer event',
          event: event,
          hitTestEntry: entry,
          informationCollector: (StringBuffer information) {
            information.writeln('Event:');
            information.writeln('  $event');
            information.writeln('Target:');
            information.write('  ${entry.target}');
          }
        ));
137 138
      }
    }
Ian Hickson's avatar
Ian Hickson committed
139 140
  }

141
  @override // from HitTestTarget
Ian Hickson's avatar
Ian Hickson committed
142 143 144 145 146 147 148 149 150
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
      gestureArena.sweep(event.pointer);
    }
  }
}
151 152

/// Variant of [FlutterErrorDetails] with extra fields for the gesture
153
/// library's binding's pointer event dispatcher ([GestureBinding.dispatchEvent]).
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
///
/// See also [FlutterErrorDetailsForPointerRouter], which is also used by the
/// gesture library.
class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
  /// Creates a [FlutterErrorDetailsForPointerEventDispatcher] object with the given
  /// arguments setting the object's properties.
  ///
  /// The gesture library calls this constructor when catching an exception
  /// that will subsequently be reported using [FlutterError.onError].
  const FlutterErrorDetailsForPointerEventDispatcher({
    dynamic exception,
    StackTrace stack,
    String library,
    String context,
    this.event,
    this.hitTestEntry,
170
    InformationCollector informationCollector,
171
    bool silent: false
172 173 174 175 176 177 178 179 180 181 182 183 184 185
  }) : super(
    exception: exception,
    stack: stack,
    library: library,
    context: context,
    informationCollector: informationCollector,
    silent: silent
  );

  /// The pointer event that was being routed when the exception was raised.
  final PointerEvent event;

  /// The hit test result entry for the object whose handleEvent method threw
  /// the exception.
186
  ///
187 188 189 190
  /// The target object itself is given by the [HitTestEntry.target] property of
  /// the hitTestEntry object.
  final HitTestEntry hitTestEntry;
}