2015-10-28 07:16:56 -07:00

271 lines
8.4 KiB
Dart

// 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;
import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'box.dart';
import 'hit_test.dart';
import 'object.dart';
import 'view.dart';
typedef void EventListener(InputEvent event);
typedef void MetricListener(Size size);
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;
}
/// A hit test entry used by [FlutterBinding]
class BindingHitTestEntry extends HitTestEntry {
const BindingHitTestEntry(HitTestTarget target, this.result) : super(target);
/// The result of the hit test
final HitTestResult result;
}
/// State used in converting ui.Event to InputEvent
class _PointerState {
_PointerState({ this.pointer, this.lastPosition });
int pointer;
Point lastPosition;
}
class _UiEventConverter {
static InputEvent convert(ui.Event event) {
if (event is ui.PointerEvent)
return convertPointerEvent(event);
// Default event
return new InputEvent(
type: event.type,
timeStamp: event.timeStamp
);
}
// 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;
static PointerInputEvent convertPointerEvent(ui.PointerEvent event) {
Point position = new Point(event.x, event.y);
_PointerState state = _stateForPointer[event.pointer];
double dx = 0.0;
double dy = 0.0;
switch (event.type) {
case 'pointerdown':
if (state == null) {
state = new _PointerState(lastPosition: position);
_stateForPointer[event.pointer] = state;
}
state.pointer = _pointerCount;
_pointerCount++;
break;
case 'pointermove':
// 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;
case 'pointerup':
case 'pointercancel':
// state == null indicates spurious events
if (state != null) {
// Only remove the pointer state when the last button has been released.
if (_hammingWeight(event.buttons) <= 1)
_stateForPointer.remove(event.pointer);
}
break;
}
int pointer = (state == null) ? event.pointer : state.pointer;
return new PointerInputEvent(
type: event.type,
timeStamp: event.timeStamp,
pointer: pointer,
kind: event.kind,
x: event.x,
y: event.y,
dx: dx,
dy: dy,
buttons: event.buttons,
down: event.down,
primary: event.primary,
obscured: event.obscured,
pressure: event.pressure,
pressureMin: event.pressureMin,
pressureMax: event.pressureMax,
distance: event.distance,
distanceMin: event.distanceMin,
distanceMax: event.distanceMax,
radiusMajor: event.radiusMajor,
radiusMinor: event.radiusMinor,
radiusMin: event.radiusMin,
radiusMax: event.radiusMax,
orientation: event.orientation,
tilt: event.tilt
);
}
}
/// The glue between the render tree and the Flutter engine
class FlutterBinding extends HitTestTarget {
FlutterBinding({ RenderBox root: null, RenderView renderViewOverride }) {
assert(_instance == null);
_instance = this;
ui.window.onEvent = _handleEvent;
ui.window.onMetricsChanged = _handleMetricsChanged;
if (renderViewOverride == null) {
_renderView = new RenderView(child: root);
_renderView.attach();
_handleMetricsChanged();
_renderView.scheduleInitialFrame();
} else {
_renderView = renderViewOverride;
}
assert(_renderView != null);
scheduler.addPersistentFrameCallback(beginFrame);
assert(_instance == this);
}
/// The singleton instance of the binding
static FlutterBinding get instance => _instance;
static FlutterBinding _instance;
/// The render tree that's attached to the output surface
RenderView get renderView => _renderView;
RenderView _renderView;
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);
void _handleMetricsChanged() {
Size size = ui.window.size;
_renderView.rootConstraints = new ViewConstraints(size: size);
for (MetricListener listener in _metricListeners)
listener(size);
}
/// Pump the rendering pipeline to generate a frame for the given time stamp
void beginFrame(Duration timeStamp) {
RenderObject.flushLayout();
_renderView.updateCompositingBits();
RenderObject.flushPaint();
_renderView.compositeFrame();
}
final List<EventListener> _eventListeners = new List<EventListener>();
/// 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);
void _handleEvent(ui.Event event) {
InputEvent ourEvent = _UiEventConverter.convert(event);
if (ourEvent is PointerInputEvent) {
_handlePointerInputEvent(ourEvent);
} else {
assert(event.type == 'back');
for (EventListener listener in _eventListeners)
listener(ourEvent);
}
}
/// A router that routes all pointer events received from the engine
final PointerRouter pointerRouter = new PointerRouter();
/// 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.
Map<int, HitTestResult> _resultForPointer = new Map<int, HitTestResult>();
void _handlePointerInputEvent(PointerInputEvent event) {
HitTestResult result = _resultForPointer[event.pointer];
switch (event.type) {
case 'pointerdown':
if (result == null) {
result = hitTest(new Point(event.x, event.y));
_resultForPointer[event.pointer] = result;
}
break;
case 'pointermove':
if (result == null) {
// 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':
if (result == null) {
// This seems to be a spurious event. Ignore it.
return;
}
// Only remove the hit test result when the last button has been released.
if (_hammingWeight(event.buttons) <= 1)
_resultForPointer.remove(event.pointer);
break;
}
dispatchEvent(event, result);
}
/// Determine which [HitTestTarget] objects are located at a given position
HitTestResult hitTest(Point position) {
HitTestResult result = new HitTestResult();
_renderView.hitTest(result, position: position);
result.add(new BindingHitTestEntry(this, result));
return result;
}
/// Dispatch the given event to the path of the given hit test result
void dispatchEvent(InputEvent event, HitTestResult result) {
assert(result != null);
for (HitTestEntry entry in result.path)
entry.target.handleEvent(event, entry);
}
void handleEvent(InputEvent e, BindingHitTestEntry entry) {
if (e is PointerInputEvent) {
PointerInputEvent event = e;
pointerRouter.route(event);
if (event.type == 'pointerdown')
GestureArena.instance.close(event.pointer);
else if (event.type == 'pointerup')
GestureArena.instance.sweep(event.pointer);
}
}
}
/// Prints a textual representation of the entire render tree
void debugDumpRenderTree() {
debugPrint(FlutterBinding.instance.renderView.toStringDeep());
}