// 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. import 'dart:ui' as ui show PointerData, PointerChange; 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; } Offset lastPosition; @override String toString() { return '_PointerState(pointer: $pointer, down: $down, lastPosition: $lastPosition)'; } } /// Converts from engine pointer data to framework pointer events. /// /// This takes [PointerDataPacket] objects, as received from the engine via /// [dart:ui.Window.onPointerDataPacket], and converts them to [PointerEvent] /// objects. class PointerEventConverter { PointerEventConverter._(); // Map from platform pointer identifiers to PointerEvent pointer identifiers. static final Map<int, _PointerState> _pointers = <int, _PointerState>{}; static _PointerState _ensureStateForPointer(ui.PointerData datum, Offset position) { return _pointers.putIfAbsent( datum.device, () => new _PointerState(position) ); } /// Expand the given packet of pointer data into a sequence of framework pointer events. /// /// 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. static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) sync* { for (ui.PointerData datum in data) { final Offset position = new Offset(datum.physicalX, datum.physicalY) / devicePixelRatio; final Duration timeStamp = datum.timeStamp; final PointerDeviceKind kind = datum.kind; assert(datum.change != null); switch (datum.change) { case ui.PointerChange.add: assert(!_pointers.containsKey(datum.device)); final _PointerState state = _ensureStateForPointer(datum, position); assert(state.lastPosition == position); yield new PointerAddedEvent( timeStamp: timeStamp, kind: kind, device: datum.device, position: position, obscured: datum.obscured, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distance: datum.distance, distanceMax: datum.distanceMax, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); break; case ui.PointerChange.hover: final bool alreadyAdded = _pointers.containsKey(datum.device); final _PointerState state = _ensureStateForPointer(datum, position); assert(!state.down); if (!alreadyAdded) { assert(state.lastPosition == position); yield new PointerAddedEvent( timeStamp: timeStamp, kind: kind, device: datum.device, position: position, obscured: datum.obscured, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distance: datum.distance, distanceMax: datum.distanceMax, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); } final Offset offset = position - state.lastPosition; state.lastPosition = position; yield new PointerHoverEvent( 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, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); state.lastPosition = position; break; case ui.PointerChange.down: final bool alreadyAdded = _pointers.containsKey(datum.device); final _PointerState state = _ensureStateForPointer(datum, position); assert(!state.down); if (!alreadyAdded) { assert(state.lastPosition == position); yield new PointerAddedEvent( timeStamp: timeStamp, kind: kind, device: datum.device, position: position, obscured: datum.obscured, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distance: datum.distance, distanceMax: datum.distanceMax, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, 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. final Offset offset = position - state.lastPosition; state.lastPosition = position; yield new PointerHoverEvent( 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, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt, synthesized: true, ); state.lastPosition = position; } state.startNewPointer(); state.setDown(); yield new PointerDownEvent( timeStamp: timeStamp, pointer: state.pointer, kind: kind, device: datum.device, position: position, buttons: datum.buttons, obscured: datum.obscured, pressure: datum.pressure, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distanceMax: datum.distanceMax, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); break; case ui.PointerChange.move: // 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 assert(_pointers.containsKey(datum.device)); final _PointerState state = _pointers[datum.device]; assert(state.down); final Offset offset = position - state.lastPosition; state.lastPosition = position; yield new PointerMoveEvent( timeStamp: timeStamp, pointer: state.pointer, kind: kind, device: datum.device, position: position, delta: offset, buttons: datum.buttons, obscured: datum.obscured, pressure: datum.pressure, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distanceMax: datum.distanceMax, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); break; case ui.PointerChange.up: case ui.PointerChange.cancel: assert(_pointers.containsKey(datum.device)); final _PointerState state = _pointers[datum.device]; assert(state.down); 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. final Offset offset = position - state.lastPosition; state.lastPosition = position; yield new PointerMoveEvent( timeStamp: timeStamp, pointer: state.pointer, kind: kind, device: datum.device, position: position, delta: offset, buttons: datum.buttons, obscured: datum.obscured, pressure: datum.pressure, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distanceMax: datum.distanceMax, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt, synthesized: true, ); state.lastPosition = position; } assert(position == state.lastPosition); state.setUp(); if (datum.change == ui.PointerChange.up) { yield new PointerUpEvent( timeStamp: timeStamp, pointer: state.pointer, kind: kind, device: datum.device, position: position, buttons: datum.buttons, obscured: datum.obscured, pressureMax: datum.pressureMax, distance: datum.distance, distanceMax: datum.distanceMax, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); } else { yield new PointerCancelEvent( 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, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); } break; case ui.PointerChange.remove: assert(_pointers.containsKey(datum.device)); final _PointerState state = _pointers[datum.device]; if (state.down) { yield new PointerCancelEvent( 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, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax, orientation: datum.orientation, tilt: datum.tilt ); } _pointers.remove(datum.device); yield new PointerRemovedEvent( timeStamp: timeStamp, kind: kind, device: datum.device, obscured: datum.obscured, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, distanceMax: datum.distanceMax, radiusMin: datum.radiusMin, radiusMax: datum.radiusMax ); break; } } } }