converter.dart 13.5 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
import 'dart:ui' as ui show PointerData, PointerChange;
Ian Hickson's avatar
Ian Hickson committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

import 'events.dart';

class _PointerState {
  _PointerState(this.lastPosition);

  int get pointer => _pointer; // The identifier used in PointerEvent objects.
  int _pointer;
  static int _pointerCount = 0;
  void startNewPointer() {
    _pointerCount += 1;
    _pointer = _pointerCount;
  }

  bool get down => _down;
  bool _down = false;
  void setDown() {
    assert(!_down);
    _down = true;
  }
  void setUp() {
    assert(_down);
    _down = false;
  }

31
  Offset lastPosition;
32 33 34 35 36

  @override
  String toString() {
    return '_PointerState(pointer: $pointer, down: $down, lastPosition: $lastPosition)';
  }
Ian Hickson's avatar
Ian Hickson committed
37 38
}

39
/// Converts from engine pointer data to framework pointer events.
40 41 42 43
///
/// This takes [PointerDataPacket] objects, as received from the engine via
/// [dart:ui.Window.onPointerDataPacket], and converts them to [PointerEvent]
/// objects.
Ian Hickson's avatar
Ian Hickson committed
44
class PointerEventConverter {
45 46
  PointerEventConverter._();

Ian Hickson's avatar
Ian Hickson committed
47
  // Map from platform pointer identifiers to PointerEvent pointer identifiers.
48
  static final Map<int, _PointerState> _pointers = <int, _PointerState>{};
Ian Hickson's avatar
Ian Hickson committed
49

50
  static _PointerState _ensureStateForPointer(ui.PointerData datum, Offset position) {
51 52
    return _pointers.putIfAbsent(
      datum.device,
53
      () => _PointerState(position)
54 55 56
    );
  }

57
  /// Expand the given packet of pointer data into a sequence of framework pointer events.
58 59 60 61 62
  ///
  /// The `devicePixelRatio` argument (usually given the value from
  /// [dart:ui.Window.devicePixelRatio]) is used to convert the incoming data
  /// from physical coordinates to logical pixels. See the discussion at
  /// [PointerEvent] for more details on the [PointerEvent] coordinate space.
63 64
  static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) sync* {
    for (ui.PointerData datum in data) {
65
      final Offset position = Offset(datum.physicalX, datum.physicalY) / devicePixelRatio;
66 67 68 69
      final double radiusMinor = _toLogicalPixels(datum.radiusMinor, devicePixelRatio);
      final double radiusMajor = _toLogicalPixels(datum.radiusMajor, devicePixelRatio);
      final double radiusMin = _toLogicalPixels(datum.radiusMin, devicePixelRatio);
      final double radiusMax = _toLogicalPixels(datum.radiusMax, devicePixelRatio);
70 71
      final Duration timeStamp = datum.timeStamp;
      final PointerDeviceKind kind = datum.kind;
72
      assert(datum.change != null);
73
      switch (datum.change) {
74 75
        case ui.PointerChange.add:
          assert(!_pointers.containsKey(datum.device));
76
          final _PointerState state = _ensureStateForPointer(datum, position);
Ian Hickson's avatar
Ian Hickson committed
77
          assert(state.lastPosition == position);
78
          yield PointerAddedEvent(
Ian Hickson's avatar
Ian Hickson committed
79 80
            timeStamp: timeStamp,
            kind: kind,
81
            device: datum.device,
Ian Hickson's avatar
Ian Hickson committed
82 83 84 85 86 87
            position: position,
            obscured: datum.obscured,
            pressureMin: datum.pressureMin,
            pressureMax: datum.pressureMax,
            distance: datum.distance,
            distanceMax: datum.distanceMax,
88 89
            radiusMin: radiusMin,
            radiusMax: radiusMax,
Ian Hickson's avatar
Ian Hickson committed
90 91 92
            orientation: datum.orientation,
            tilt: datum.tilt
          );
93 94 95
          break;
        case ui.PointerChange.hover:
          final bool alreadyAdded = _pointers.containsKey(datum.device);
96
          final _PointerState state = _ensureStateForPointer(datum, position);
97 98 99
          assert(!state.down);
          if (!alreadyAdded) {
            assert(state.lastPosition == position);
100
            yield PointerAddedEvent(
101 102 103 104 105 106 107 108 109
              timeStamp: timeStamp,
              kind: kind,
              device: datum.device,
              position: position,
              obscured: datum.obscured,
              pressureMin: datum.pressureMin,
              pressureMax: datum.pressureMax,
              distance: datum.distance,
              distanceMax: datum.distanceMax,
110 111
              radiusMin: radiusMin,
              radiusMax: radiusMax,
112 113 114 115
              orientation: datum.orientation,
              tilt: datum.tilt
            );
          }
116
          final Offset offset = position - state.lastPosition;
117
          state.lastPosition = position;
118
          yield PointerHoverEvent(
119 120 121 122 123 124 125 126 127 128 129
            timeStamp: timeStamp,
            kind: kind,
            device: datum.device,
            position: position,
            delta: offset,
            buttons: datum.buttons,
            obscured: datum.obscured,
            pressureMin: datum.pressureMin,
            pressureMax: datum.pressureMax,
            distance: datum.distance,
            distanceMax: datum.distanceMax,
130 131 132 133
            radiusMajor: radiusMajor,
            radiusMinor: radiusMinor,
            radiusMin: radiusMin,
            radiusMax: radiusMax,
134 135 136 137 138 139 140
            orientation: datum.orientation,
            tilt: datum.tilt
          );
          state.lastPosition = position;
          break;
        case ui.PointerChange.down:
          final bool alreadyAdded = _pointers.containsKey(datum.device);
141
          final _PointerState state = _ensureStateForPointer(datum, position);
142 143 144
          assert(!state.down);
          if (!alreadyAdded) {
            assert(state.lastPosition == position);
145
            yield PointerAddedEvent(
146 147 148 149 150 151 152 153 154
              timeStamp: timeStamp,
              kind: kind,
              device: datum.device,
              position: position,
              obscured: datum.obscured,
              pressureMin: datum.pressureMin,
              pressureMax: datum.pressureMax,
              distance: datum.distance,
              distanceMax: datum.distanceMax,
155 156
              radiusMin: radiusMin,
              radiusMax: radiusMax,
157 158 159 160 161 162 163 164
              orientation: datum.orientation,
              tilt: datum.tilt
            );
          }
          if (state.lastPosition != position) {
            // Not all sources of pointer packets respect the invariant that
            // they hover the pointer to the down location before sending the
            // down event. We restore the invariant here for our clients.
165
            final Offset offset = position - state.lastPosition;
166
            state.lastPosition = position;
167
            yield PointerHoverEvent(
168 169 170 171 172 173 174 175 176 177 178
              timeStamp: timeStamp,
              kind: kind,
              device: datum.device,
              position: position,
              delta: offset,
              buttons: datum.buttons,
              obscured: datum.obscured,
              pressureMin: datum.pressureMin,
              pressureMax: datum.pressureMax,
              distance: datum.distance,
              distanceMax: datum.distanceMax,
179 180 181 182
              radiusMajor: radiusMajor,
              radiusMinor: radiusMinor,
              radiusMin: radiusMin,
              radiusMax: radiusMax,
183
              orientation: datum.orientation,
184 185
              tilt: datum.tilt,
              synthesized: true,
186 187 188 189 190
            );
            state.lastPosition = position;
          }
          state.startNewPointer();
          state.setDown();
191
          yield PointerDownEvent(
Ian Hickson's avatar
Ian Hickson committed
192 193 194
            timeStamp: timeStamp,
            pointer: state.pointer,
            kind: kind,
195
            device: datum.device,
Ian Hickson's avatar
Ian Hickson committed
196
            position: position,
197
            buttons: datum.buttons,
Ian Hickson's avatar
Ian Hickson committed
198 199 200 201 202
            obscured: datum.obscured,
            pressure: datum.pressure,
            pressureMin: datum.pressureMin,
            pressureMax: datum.pressureMax,
            distanceMax: datum.distanceMax,
203 204 205 206
            radiusMajor: radiusMajor,
            radiusMinor: radiusMinor,
            radiusMin: radiusMin,
            radiusMax: radiusMax,
Ian Hickson's avatar
Ian Hickson committed
207 208 209 210
            orientation: datum.orientation,
            tilt: datum.tilt
          );
          break;
211
        case ui.PointerChange.move:
Ian Hickson's avatar
Ian Hickson committed
212 213 214
          // If the service starts supporting hover pointers, then it must also
          // start sending us ADDED and REMOVED data points.
          // See also: https://github.com/flutter/flutter/issues/720
215
          assert(_pointers.containsKey(datum.device));
216
          final _PointerState state = _pointers[datum.device];
Ian Hickson's avatar
Ian Hickson committed
217
          assert(state.down);
218
          final Offset offset = position - state.lastPosition;
Ian Hickson's avatar
Ian Hickson committed
219
          state.lastPosition = position;
220
          yield PointerMoveEvent(
Ian Hickson's avatar
Ian Hickson committed
221 222 223
            timeStamp: timeStamp,
            pointer: state.pointer,
            kind: kind,
224
            device: datum.device,
Ian Hickson's avatar
Ian Hickson committed
225 226
            position: position,
            delta: offset,
227
            buttons: datum.buttons,
Ian Hickson's avatar
Ian Hickson committed
228 229 230 231 232
            obscured: datum.obscured,
            pressure: datum.pressure,
            pressureMin: datum.pressureMin,
            pressureMax: datum.pressureMax,
            distanceMax: datum.distanceMax,
233 234 235 236
            radiusMajor: radiusMajor,
            radiusMinor: radiusMinor,
            radiusMin: radiusMin,
            radiusMax: radiusMax,
Ian Hickson's avatar
Ian Hickson committed
237 238 239 240
            orientation: datum.orientation,
            tilt: datum.tilt
          );
          break;
241 242
        case ui.PointerChange.up:
        case ui.PointerChange.cancel:
243
          assert(_pointers.containsKey(datum.device));
244
          final _PointerState state = _pointers[datum.device];
Ian Hickson's avatar
Ian Hickson committed
245
          assert(state.down);
246 247 248 249 250 251
          if (position != state.lastPosition) {
            // Not all sources of pointer packets respect the invariant that
            // they move the pointer to the up location before sending the up
            // event. For example, in the iOS simulator, of you drag outside the
            // window, you'll get a stream of pointers that violates that
            // invariant. We restore the invariant here for our clients.
252
            final Offset offset = position - state.lastPosition;
253
            state.lastPosition = position;
254
            yield PointerMoveEvent(
255 256 257
              timeStamp: timeStamp,
              pointer: state.pointer,
              kind: kind,
258
              device: datum.device,
259 260
              position: position,
              delta: offset,
261
              buttons: datum.buttons,
262 263 264 265 266
              obscured: datum.obscured,
              pressure: datum.pressure,
              pressureMin: datum.pressureMin,
              pressureMax: datum.pressureMax,
              distanceMax: datum.distanceMax,
267 268 269 270
              radiusMajor: radiusMajor,
              radiusMinor: radiusMinor,
              radiusMin: radiusMin,
              radiusMax: radiusMax,
271
              orientation: datum.orientation,
272 273
              tilt: datum.tilt,
              synthesized: true,
274 275 276
            );
            state.lastPosition = position;
          }
Ian Hickson's avatar
Ian Hickson committed
277 278
          assert(position == state.lastPosition);
          state.setUp();
279
          if (datum.change == ui.PointerChange.up) {
280
            yield PointerUpEvent(
Ian Hickson's avatar
Ian Hickson committed
281 282 283
              timeStamp: timeStamp,
              pointer: state.pointer,
              kind: kind,
284
              device: datum.device,
Ian Hickson's avatar
Ian Hickson committed
285
              position: position,
286
              buttons: datum.buttons,
Ian Hickson's avatar
Ian Hickson committed
287
              obscured: datum.obscured,
288 289
              pressure: datum.pressure,
              pressureMin: datum.pressureMin,
Ian Hickson's avatar
Ian Hickson committed
290 291 292
              pressureMax: datum.pressureMax,
              distance: datum.distance,
              distanceMax: datum.distanceMax,
293 294 295 296
              radiusMajor: radiusMajor,
              radiusMinor: radiusMinor,
              radiusMin: radiusMin,
              radiusMax: radiusMax,
Ian Hickson's avatar
Ian Hickson committed
297 298 299 300
              orientation: datum.orientation,
              tilt: datum.tilt
            );
          } else {
301
            yield PointerCancelEvent(
Ian Hickson's avatar
Ian Hickson committed
302 303 304
              timeStamp: timeStamp,
              pointer: state.pointer,
              kind: kind,
305
              device: datum.device,
Ian Hickson's avatar
Ian Hickson committed
306
              position: position,
307
              buttons: datum.buttons,
Ian Hickson's avatar
Ian Hickson committed
308 309 310 311 312
              obscured: datum.obscured,
              pressureMin: datum.pressureMin,
              pressureMax: datum.pressureMax,
              distance: datum.distance,
              distanceMax: datum.distanceMax,
313 314 315 316
              radiusMajor: radiusMajor,
              radiusMinor: radiusMinor,
              radiusMin: radiusMin,
              radiusMax: radiusMax,
Ian Hickson's avatar
Ian Hickson committed
317 318 319 320
              orientation: datum.orientation,
              tilt: datum.tilt
            );
          }
321 322
          break;
        case ui.PointerChange.remove:
323
          assert(_pointers.containsKey(datum.device));
324
          final _PointerState state = _pointers[datum.device];
325
          if (state.down) {
326
            yield PointerCancelEvent(
327 328 329 330 331 332 333 334 335 336 337
              timeStamp: timeStamp,
              pointer: state.pointer,
              kind: kind,
              device: datum.device,
              position: position,
              buttons: datum.buttons,
              obscured: datum.obscured,
              pressureMin: datum.pressureMin,
              pressureMax: datum.pressureMax,
              distance: datum.distance,
              distanceMax: datum.distanceMax,
338 339 340 341
              radiusMajor: radiusMajor,
              radiusMinor: radiusMinor,
              radiusMin: radiusMin,
              radiusMax: radiusMax,
342 343 344 345 346
              orientation: datum.orientation,
              tilt: datum.tilt
            );
          }
          _pointers.remove(datum.device);
347
          yield PointerRemovedEvent(
Ian Hickson's avatar
Ian Hickson committed
348 349
            timeStamp: timeStamp,
            kind: kind,
350
            device: datum.device,
Ian Hickson's avatar
Ian Hickson committed
351 352 353 354
            obscured: datum.obscured,
            pressureMin: datum.pressureMin,
            pressureMax: datum.pressureMax,
            distanceMax: datum.distanceMax,
355 356
            radiusMin: radiusMin,
            radiusMax: radiusMax
Ian Hickson's avatar
Ian Hickson committed
357 358 359 360 361
          );
          break;
      }
    }
  }
362 363 364

  static double _toLogicalPixels(double physicalPixels, double devicePixelRatio) =>
      physicalPixels == null ? null : physicalPixels / devicePixelRatio;
Ian Hickson's avatar
Ian Hickson committed
365
}