binding.dart 9.48 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 'dart:typed_data';
6
import 'dart:ui' as ui;
7

8 9
import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
10
import 'package:flutter/rendering.dart';
11 12 13
import 'package:mojo/bindings.dart' as bindings;
import 'package:mojo/core.dart' as core;
import 'package:sky_services/pointer/pointer.mojom.dart';
14 15 16 17 18

import 'box.dart';
import 'hit_test.dart';
import 'object.dart';
import 'view.dart';
19

20 21 22
typedef void EventListener(InputEvent event);
typedef void MetricListener(Size size);

23 24 25 26 27 28 29 30 31 32 33
int _hammingWeight(int value) {
  if (value == 0)
    return 0;
  int weight = 0;
  for (int i = 0; i < value.bitLength; ++i) {
    if (value & (1 << i) != 0)
      ++weight;
  }
  return weight;
}

34
/// State used in converting PointerPackets to PointerInputEvents
35 36 37 38 39 40
class _PointerState {
  _PointerState({ this.pointer, this.lastPosition });
  int pointer;
  Point lastPosition;
}

41
class _PointerEventConverter {
42 43 44 45 46
  // Map actual input pointer value to a unique value
  // Since events are serialized we can just use a counter
  static Map<int, _PointerState> _stateForPointer = new Map<int, _PointerState>();
  static int _pointerCount = 0;

47 48 49 50 51 52
  static List<PointerInputEvent> convertPointerPacket(PointerPacket packet) {
    return packet.pointers.map(_convertPointer).toList();
  }

  static PointerInputEvent _convertPointer(Pointer pointer) {
    Point position = new Point(pointer.x, pointer.y);
53

54
    _PointerState state = _stateForPointer[pointer.pointer];
55 56
    double dx = 0.0;
    double dy = 0.0;
57 58 59 60
    String eventType;
    switch (pointer.type) {
      case PointerType.DOWN:
        eventType = 'pointerdown';
61 62
        if (state == null) {
          state = new _PointerState(lastPosition: position);
63
          _stateForPointer[pointer.pointer] = state;
64 65 66 67
        }
        state.pointer = _pointerCount;
        _pointerCount++;
        break;
68 69
      case PointerType.MOVE:
        eventType = 'pointermove';
70 71 72 73 74 75 76
        // state == null means the pointer is hovering
        if (state != null) {
          dx = position.x - state.lastPosition.x;
          dy = position.y - state.lastPosition.y;
          state.lastPosition = position;
        }
        break;
77 78 79
      case PointerType.UP:
      case PointerType.CANCEL:
        eventType = (pointer.type == PointerType.UP) ? 'pointerup' : 'pointercancel';
80 81 82
        // state == null indicates spurious events
        if (state != null) {
          // Only remove the pointer state when the last button has been released.
83 84
          if (_hammingWeight(pointer.buttons) <= 1)
            _stateForPointer.remove(pointer.pointer);
85 86 87 88
        }
        break;
    }

89
    int pointerIndex = (state == null) ? pointer.pointer : state.pointer;
90

91
    return new PointerInputEvent(
92
       type: eventType,
93
       timeStamp: new Duration(microseconds: pointer.timeStamp),
94 95 96 97
       pointer: pointerIndex,
       kind: _mapPointerKindToString(pointer.kind),
       x: pointer.x,
       y: pointer.y,
98 99
       dx: dx,
       dy: dy,
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
       buttons: pointer.buttons,
       down: pointer.down,
       primary: pointer.primary,
       obscured: pointer.obscured,
       pressure: pointer.pressure,
       pressureMin: pointer.pressureMin,
       pressureMax: pointer.pressureMax,
       distance: pointer.distance,
       distanceMin: pointer.distanceMin,
       distanceMax: pointer.distanceMax,
       radiusMajor: pointer.radiusMajor,
       radiusMinor: pointer.radiusMinor,
       radiusMin: pointer.radiusMin,
       radiusMax: pointer.radiusMax,
       orientation: pointer.orientation,
       tilt: pointer.tilt
116 117 118
     );
  }

119 120 121 122 123 124 125 126
  static String _mapPointerKindToString(PointerKind kind) {
    switch (kind) {
      case PointerKind.TOUCH:
        return 'touch';
      case PointerKind.MOUSE:
        return 'mouse';
      case PointerKind.STYLUS:
        return 'stylus';
Hixie's avatar
Hixie committed
127 128
      case PointerKind.INVERTED_STYLUS:
        return 'invertedStylus';
129
    }
Hixie's avatar
Hixie committed
130 131
    assert(false);
    return '';
132
  }
133 134
}

135
/// The glue between the render tree and the Flutter engine
136
class FlutterBinding extends HitTestTarget {
137

138
  FlutterBinding({ RenderBox root: null, RenderView renderViewOverride }) {
139 140 141
    assert(_instance == null);
    _instance = this;

142
    ui.window.onEvent = _handleEvent;
143
    ui.window.onPointerPacket = _handlePointerPacket;
144
    ui.window.onMetricsChanged = _handleMetricsChanged;
145 146

    if (renderViewOverride == null) {
147
      _renderView = new RenderView(child: root);
148
      _renderView.attach();
149
      _handleMetricsChanged();
150
      _renderView.scheduleInitialFrame();
151 152 153 154
    } else {
      _renderView = renderViewOverride;
    }
    assert(_renderView != null);
155
    scheduler.addPersistentFrameCallback(_handlePersistentFrameCallback);
156 157 158 159

    assert(_instance == this);
  }

160
  /// The singleton instance of the binding
161 162
  static FlutterBinding get instance => _instance;
  static FlutterBinding _instance;
163

164
  /// The render tree that's attached to the output surface
165
  RenderView get renderView => _renderView;
166
  RenderView _renderView;
167

168 169 170 171 172 173 174 175
  final List<MetricListener> _metricListeners = new List<MetricListener>();

  /// Calls listener for every event that isn't localized to a given view coordinate
  void addMetricListener(MetricListener listener) => _metricListeners.add(listener);

  /// Stops calling listener for every event that isn't localized to a given view coordinate
  bool removeMetricListener(MetricListener listener) => _metricListeners.remove(listener);

176
  void _handleMetricsChanged() {
177
    Size size = ui.window.size;
178 179 180
    _renderView.rootConstraints = new ViewConstraints(size: size);
    for (MetricListener listener in _metricListeners)
      listener(size);
181 182
  }

183 184 185 186
  void _handlePersistentFrameCallback(Duration timeStamp) {
    beginFrame();
  }

187
  /// Pump the rendering pipeline to generate a frame for the given time stamp
188
  void beginFrame() {
189
    RenderObject.flushLayout();
Hixie's avatar
Hixie committed
190
    _renderView.updateCompositingBits();
191
    RenderObject.flushPaint();
192
    _renderView.compositeFrame();
193 194 195
  }

  final List<EventListener> _eventListeners = new List<EventListener>();
196 197 198 199 200 201

  /// Calls listener for every event that isn't localized to a given view coordinate
  void addEventListener(EventListener listener) => _eventListeners.add(listener);

  /// Stops calling listener for every event that isn't localized to a given view coordinate
  bool removeEventListener(EventListener listener) => _eventListeners.remove(listener);
202

203
  // TODO(abarth): The engine should give us the timeStamp in Durations.
204 205 206 207
  void _handleEvent(String eventType, double timeStamp) {
    assert(eventType == 'back');
    InputEvent ourEvent = new InputEvent(
      type: eventType,
208
      timeStamp: new Duration(microseconds: timeStamp.round())
209 210 211 212 213 214 215
    );
    for (EventListener listener in _eventListeners)
      listener(ourEvent);
  }

  void _handlePointerPacket(ByteData serializedPacket) {
    bindings.Message message = new bindings.Message(
216 217 218 219
        serializedPacket,
        <core.MojoHandle>[],
        serializedPacket.lengthInBytes,
        0);
220 221 222
    PointerPacket packet = PointerPacket.deserialize(message);
    for (PointerInputEvent event in _PointerEventConverter.convertPointerPacket(packet)) {
      _handlePointerInputEvent(event);
223 224 225
    }
  }

226
  /// A router that routes all pointer events received from the engine
Adam Barth's avatar
Adam Barth committed
227 228
  final PointerRouter pointerRouter = new PointerRouter();

229 230 231
  /// State for all pointers which are currently down.
  /// We do not track the state of hovering pointers because we need
  /// to hit-test them on each movement.
232
  Map<int, HitTestResult> _resultForPointer = new Map<int, HitTestResult>();
233

234
  void _handlePointerInputEvent(PointerInputEvent event) {
235
    HitTestResult result = _resultForPointer[event.pointer];
236 237
    switch (event.type) {
      case 'pointerdown':
238 239 240
        if (result == null) {
          result = hitTest(new Point(event.x, event.y));
          _resultForPointer[event.pointer] = result;
241 242 243
        }
        break;
      case 'pointermove':
244
        if (result == null) {
245 246 247 248 249 250 251
          // The pointer is hovering, ignore it for now since we don't
          // know what to do with it yet.
          return;
        }
        break;
      case 'pointerup':
      case 'pointercancel':
252
        if (result == null) {
253 254 255
          // This seems to be a spurious event.  Ignore it.
          return;
        }
256
        // Only remove the hit test result when the last button has been released.
257
        if (_hammingWeight(event.buttons) <= 1)
258
          _resultForPointer.remove(event.pointer);
259
        break;
260
    }
261
    dispatchEvent(event, result);
262 263
  }

264
  /// Determine which [HitTestTarget] objects are located at a given position
265 266 267
  HitTestResult hitTest(Point position) {
    HitTestResult result = new HitTestResult();
    _renderView.hitTest(result, position: position);
Ian Hickson's avatar
Ian Hickson committed
268
    result.add(new HitTestEntry(this));
269 270 271
    return result;
  }

272
  /// Dispatch the given event to the path of the given hit test result
273
  void dispatchEvent(InputEvent event, HitTestResult result) {
274
    assert(result != null);
Adam Barth's avatar
Adam Barth committed
275 276
    for (HitTestEntry entry in result.path)
      entry.target.handleEvent(event, entry);
277 278
  }

Ian Hickson's avatar
Ian Hickson committed
279
  void handleEvent(InputEvent e, HitTestEntry entry) {
280 281
    if (e is PointerInputEvent) {
      PointerInputEvent event = e;
Adam Barth's avatar
Adam Barth committed
282 283 284
      pointerRouter.route(event);
      if (event.type == 'pointerdown')
        GestureArena.instance.close(event.pointer);
285 286
      else if (event.type == 'pointerup')
        GestureArena.instance.sweep(event.pointer);
Adam Barth's avatar
Adam Barth committed
287
    }
288
  }
289
}
290

291 292
/// Prints a textual representation of the entire render tree
void debugDumpRenderTree() {
293
  debugPrint(FlutterBinding.instance.renderView.toStringDeep());
294
}