mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Optimizes sprite transformations for box to node
Enabling/disabling of handling multiple pointers Adds basic touch handling R=abarth@chromium.org Review URL: https://codereview.chromium.org/1179413009.
This commit is contained in:
parent
2b126efbc8
commit
1422bb82aa
@ -14,66 +14,66 @@ class GameDemoBox extends SpriteBox {
|
||||
int _secondPointer = -1;
|
||||
Point _firstPointerDownPos;
|
||||
|
||||
void handleEvent(Event event, BoxHitTestEntry entry) {
|
||||
if (event is PointerEvent) {
|
||||
Point pointerPos = new Point(event.x, event.y);
|
||||
int pointer = event.pointer;
|
||||
|
||||
switch (event.type) {
|
||||
case 'pointerdown':
|
||||
if (_firstPointer == -1) {
|
||||
// Assign the first pointer
|
||||
_firstPointer = pointer;
|
||||
_firstPointerDownPos = pointerPos;
|
||||
}
|
||||
else if (_secondPointer == -1) {
|
||||
// Assign second pointer
|
||||
_secondPointer = pointer;
|
||||
_gameWorld.controlFire();
|
||||
}
|
||||
else {
|
||||
// There is a pointer used for steering, let's fire instead
|
||||
_gameWorld.controlFire();
|
||||
}
|
||||
break;
|
||||
case 'pointermove':
|
||||
if (pointer == _firstPointer) {
|
||||
// Handle turning control
|
||||
double joystickX = 0.0;
|
||||
double deltaX = pointerPos.x - _firstPointerDownPos.x;
|
||||
if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) {
|
||||
joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold);
|
||||
if (joystickX > 1.0) joystickX = 1.0;
|
||||
if (joystickX < -1.0) joystickX = -1.0;
|
||||
}
|
||||
|
||||
double joystickY = 0.0;
|
||||
double deltaY = pointerPos.y - _firstPointerDownPos.y;
|
||||
if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) {
|
||||
joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold);
|
||||
if (joystickY > 1.0) joystickY = 1.0;
|
||||
if (joystickY < -1.0) joystickY = -1.0;
|
||||
}
|
||||
|
||||
_gameWorld.controlSteering(joystickX, joystickY);
|
||||
}
|
||||
break;
|
||||
case 'pointerup':
|
||||
case 'pointercancel':
|
||||
if (pointer == _firstPointer) {
|
||||
// Un-assign the first pointer
|
||||
_firstPointer = -1;
|
||||
_firstPointerDownPos = null;
|
||||
_gameWorld.controlSteering(0.0, 0.0);
|
||||
}
|
||||
else if (pointer == _secondPointer) {
|
||||
_secondPointer = -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// void handleEvent(Event event, BoxHitTestEntry entry) {
|
||||
// if (event is PointerEvent) {
|
||||
// Point pointerPos = new Point(event.x, event.y);
|
||||
// int pointer = event.pointer;
|
||||
//
|
||||
// switch (event.type) {
|
||||
// case 'pointerdown':
|
||||
// if (_firstPointer == -1) {
|
||||
// // Assign the first pointer
|
||||
// _firstPointer = pointer;
|
||||
// _firstPointerDownPos = pointerPos;
|
||||
// }
|
||||
// else if (_secondPointer == -1) {
|
||||
// // Assign second pointer
|
||||
// _secondPointer = pointer;
|
||||
// _gameWorld.controlFire();
|
||||
// }
|
||||
// else {
|
||||
// // There is a pointer used for steering, let's fire instead
|
||||
// _gameWorld.controlFire();
|
||||
// }
|
||||
// break;
|
||||
// case 'pointermove':
|
||||
// if (pointer == _firstPointer) {
|
||||
// // Handle turning control
|
||||
// double joystickX = 0.0;
|
||||
// double deltaX = pointerPos.x - _firstPointerDownPos.x;
|
||||
// if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) {
|
||||
// joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold);
|
||||
// if (joystickX > 1.0) joystickX = 1.0;
|
||||
// if (joystickX < -1.0) joystickX = -1.0;
|
||||
// }
|
||||
//
|
||||
// double joystickY = 0.0;
|
||||
// double deltaY = pointerPos.y - _firstPointerDownPos.y;
|
||||
// if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) {
|
||||
// joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold);
|
||||
// if (joystickY > 1.0) joystickY = 1.0;
|
||||
// if (joystickY < -1.0) joystickY = -1.0;
|
||||
// }
|
||||
//
|
||||
// _gameWorld.controlSteering(joystickX, joystickY);
|
||||
// }
|
||||
// break;
|
||||
// case 'pointerup':
|
||||
// case 'pointercancel':
|
||||
// if (pointer == _firstPointer) {
|
||||
// // Un-assign the first pointer
|
||||
// _firstPointer = -1;
|
||||
// _firstPointerDownPos = null;
|
||||
// _gameWorld.controlSteering(0.0, 0.0);
|
||||
// }
|
||||
// else if (pointer == _secondPointer) {
|
||||
// _secondPointer = -1;
|
||||
// }
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@ -70,6 +70,9 @@ class GameDemoWorld extends NodeWithSize {
|
||||
|
||||
// Add nebula
|
||||
addNebula();
|
||||
|
||||
userInteractionEnabled = true;
|
||||
handleMultiplePointers = true;
|
||||
}
|
||||
|
||||
// Methods for adding game objects
|
||||
@ -200,6 +203,73 @@ class GameDemoWorld extends NodeWithSize {
|
||||
void controlFire() {
|
||||
addLaser();
|
||||
}
|
||||
|
||||
// Handle pointer events
|
||||
|
||||
int _firstPointer = -1;
|
||||
int _secondPointer = -1;
|
||||
Point _firstPointerDownPos;
|
||||
|
||||
bool handleEvent(SpriteBoxEvent event) {
|
||||
Point pointerPos = convertPointToNodeSpace(event.boxPosition);
|
||||
int pointer = event.pointer;
|
||||
|
||||
switch (event.type) {
|
||||
case 'pointerdown':
|
||||
if (_firstPointer == -1) {
|
||||
// Assign the first pointer
|
||||
_firstPointer = pointer;
|
||||
_firstPointerDownPos = pointerPos;
|
||||
}
|
||||
else if (_secondPointer == -1) {
|
||||
// Assign second pointer
|
||||
_secondPointer = pointer;
|
||||
controlFire();
|
||||
}
|
||||
else {
|
||||
// There is a pointer used for steering, let's fire instead
|
||||
controlFire();
|
||||
}
|
||||
break;
|
||||
case 'pointermove':
|
||||
if (pointer == _firstPointer) {
|
||||
// Handle turning control
|
||||
double joystickX = 0.0;
|
||||
double deltaX = pointerPos.x - _firstPointerDownPos.x;
|
||||
if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) {
|
||||
joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold);
|
||||
if (joystickX > 1.0) joystickX = 1.0;
|
||||
if (joystickX < -1.0) joystickX = -1.0;
|
||||
}
|
||||
|
||||
double joystickY = 0.0;
|
||||
double deltaY = pointerPos.y - _firstPointerDownPos.y;
|
||||
if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) {
|
||||
joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold);
|
||||
if (joystickY > 1.0) joystickY = 1.0;
|
||||
if (joystickY < -1.0) joystickY = -1.0;
|
||||
}
|
||||
|
||||
controlSteering(joystickX, joystickY);
|
||||
}
|
||||
break;
|
||||
case 'pointerup':
|
||||
case 'pointercancel':
|
||||
if (pointer == _firstPointer) {
|
||||
// Un-assign the first pointer
|
||||
_firstPointer = -1;
|
||||
_firstPointerDownPos = null;
|
||||
controlSteering(0.0, 0.0);
|
||||
}
|
||||
else if (pointer == _secondPointer) {
|
||||
_secondPointer = -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Game objects
|
||||
@ -230,6 +300,18 @@ class Asteroid extends Sprite {
|
||||
|
||||
_movementVector = new Point(_rand.nextDouble() * _maxAsteroidSpeed * 2 - _maxAsteroidSpeed,
|
||||
_rand.nextDouble() * _maxAsteroidSpeed * 2 - _maxAsteroidSpeed);
|
||||
|
||||
userInteractionEnabled = true;
|
||||
}
|
||||
|
||||
bool handleEvent(SpriteBoxEvent event) {
|
||||
if (event.type == "pointerdown") {
|
||||
colorOverlay = new Color(0x99ff0000);
|
||||
}
|
||||
else if (event.type == "pointerup") {
|
||||
colorOverlay = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,10 @@ class Node {
|
||||
|
||||
Point _position;
|
||||
double _rotation;
|
||||
|
||||
bool _isMatrixDirty;
|
||||
|
||||
Matrix4 _transformMatrix;
|
||||
Matrix4 _transformMatrixFromWorld;
|
||||
Matrix4 _transformMatrixNodeToBox;
|
||||
Matrix4 _transformMatrixBoxToNode;
|
||||
|
||||
double _scaleX;
|
||||
double _scaleY;
|
||||
@ -30,6 +30,10 @@ class Node {
|
||||
|
||||
bool paused = false;
|
||||
|
||||
bool _userInteractionEnabled = false;
|
||||
bool handleMultiplePointers = false;
|
||||
int _handlingPointer;
|
||||
|
||||
List<Node>_children;
|
||||
|
||||
// Constructors
|
||||
@ -38,7 +42,6 @@ class Node {
|
||||
_rotation = 0.0;
|
||||
_position = Point.origin;
|
||||
_scaleX = _scaleY = 1.0;
|
||||
_isMatrixDirty = false;
|
||||
_transformMatrix = new Matrix4.identity();
|
||||
_children = [];
|
||||
_childrenNeedSorting = false;
|
||||
@ -56,20 +59,23 @@ class Node {
|
||||
double get rotation => _rotation;
|
||||
|
||||
void set rotation(double rotation) {
|
||||
assert(rotation != null);
|
||||
_rotation = rotation;
|
||||
_isMatrixDirty = true;
|
||||
_invalidateTransformMatrix();
|
||||
}
|
||||
|
||||
Point get position => _position;
|
||||
|
||||
void set position(Point position) {
|
||||
assert(position != null);
|
||||
_position = position;
|
||||
_isMatrixDirty = true;
|
||||
_invalidateTransformMatrix();
|
||||
}
|
||||
|
||||
double get zPosition => _zPosition;
|
||||
|
||||
void set zPosition(double zPosition) {
|
||||
assert(zPosition != null);
|
||||
_zPosition = zPosition;
|
||||
if (_parent != null) {
|
||||
_parent._childrenNeedSorting = true;
|
||||
@ -82,8 +88,9 @@ class Node {
|
||||
}
|
||||
|
||||
void set scale(double scale) {
|
||||
assert(scale != null);
|
||||
_scaleX = _scaleY = scale;
|
||||
_isMatrixDirty = true;
|
||||
_invalidateTransformMatrix();
|
||||
}
|
||||
|
||||
List<Node> get children => _children;
|
||||
@ -91,6 +98,7 @@ class Node {
|
||||
// Adding and removing children
|
||||
|
||||
void addChild(Node child) {
|
||||
assert(child != null);
|
||||
assert(child._parent == null);
|
||||
|
||||
_childrenNeedSorting = true;
|
||||
@ -99,12 +107,15 @@ class Node {
|
||||
child._spriteBox = this._spriteBox;
|
||||
_childrenLastAddedOrder += 1;
|
||||
child._addedOrder = _childrenLastAddedOrder;
|
||||
if (_spriteBox != null) _spriteBox._eventTargets = null;
|
||||
}
|
||||
|
||||
void removeChild(Node child) {
|
||||
assert(child != null);
|
||||
if (_children.remove(child)) {
|
||||
child._parent = null;
|
||||
child._spriteBox = null;
|
||||
if (_spriteBox != null) _spriteBox._eventTargets = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,12 +131,13 @@ class Node {
|
||||
}
|
||||
_children = [];
|
||||
_childrenNeedSorting = false;
|
||||
if (_spriteBox != null) _spriteBox._eventTargets = null;
|
||||
}
|
||||
|
||||
// Calculating the transformation matrix
|
||||
|
||||
Matrix4 get transformMatrix {
|
||||
if (!_isMatrixDirty) {
|
||||
if (_transformMatrix != null) {
|
||||
return _transformMatrix;
|
||||
}
|
||||
|
||||
@ -148,42 +160,58 @@ class Node {
|
||||
}
|
||||
|
||||
// Create transformation matrix for scale, position and rotation
|
||||
_transformMatrix.setValues(cy * _scaleX, sy * _scaleX, 0.0, 0.0,
|
||||
_transformMatrix = new Matrix4(cy * _scaleX, sy * _scaleX, 0.0, 0.0,
|
||||
-sx * _scaleY, cx * _scaleY, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
_position.x, _position.y, 0.0, 1.0
|
||||
);
|
||||
_position.x, _position.y, 0.0, 1.0);
|
||||
|
||||
return _transformMatrix;
|
||||
}
|
||||
|
||||
void _invalidateTransformMatrix() {
|
||||
_transformMatrix = null;
|
||||
_invalidateToBoxTransformMatrix();
|
||||
}
|
||||
|
||||
void _invalidateToBoxTransformMatrix () {
|
||||
_transformMatrixNodeToBox = null;
|
||||
_transformMatrixBoxToNode = null;
|
||||
|
||||
for (Node child in children) {
|
||||
child._invalidateToBoxTransformMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
// Transforms to other nodes
|
||||
|
||||
Matrix4 _nodeToBoxMatrix() {
|
||||
assert(_spriteBox != null);
|
||||
|
||||
Matrix4 t = transformMatrix;
|
||||
|
||||
// Apply transforms from parents
|
||||
Node p = this.parent;
|
||||
while (p != null) {
|
||||
t = new Matrix4.copy(p.transformMatrix).multiply(t);
|
||||
p = p.parent;
|
||||
if (_transformMatrixNodeToBox != null) {
|
||||
return _transformMatrixNodeToBox;
|
||||
}
|
||||
|
||||
// Apply transform from sprite box
|
||||
t = new Matrix4.copy(_spriteBox.transformMatrix).multiply(t);
|
||||
|
||||
return t;
|
||||
if (_parent == null) {
|
||||
// Base case, we are at the top
|
||||
assert(this == _spriteBox.rootNode);
|
||||
_transformMatrixNodeToBox = new Matrix4.copy(_spriteBox.transformMatrix).multiply(transformMatrix);
|
||||
}
|
||||
else {
|
||||
_transformMatrixNodeToBox = new Matrix4.copy(_parent._nodeToBoxMatrix()).multiply(transformMatrix);
|
||||
}
|
||||
return _transformMatrixNodeToBox;
|
||||
}
|
||||
|
||||
Matrix4 _boxToNodeMatrix() {
|
||||
assert(_spriteBox != null);
|
||||
|
||||
Matrix4 t = _nodeToBoxMatrix();
|
||||
t.invert();
|
||||
if (_transformMatrixBoxToNode != null) {
|
||||
return _transformMatrixBoxToNode;
|
||||
}
|
||||
|
||||
return t;
|
||||
_transformMatrixBoxToNode = new Matrix4.copy(_nodeToBoxMatrix());
|
||||
_transformMatrixBoxToNode.invert();
|
||||
|
||||
return _transformMatrixBoxToNode;
|
||||
}
|
||||
|
||||
Point convertPointToNodeSpace(Point boxPoint) {
|
||||
@ -225,6 +253,7 @@ class Node {
|
||||
// Rendering
|
||||
|
||||
void visit(PictureRecorder canvas) {
|
||||
assert(canvas != null);
|
||||
if (!visible) return;
|
||||
|
||||
prePaint(canvas);
|
||||
@ -241,7 +270,6 @@ class Node {
|
||||
}
|
||||
|
||||
void paint(PictureRecorder canvas) {
|
||||
|
||||
}
|
||||
|
||||
void visitChildren(PictureRecorder canvas) {
|
||||
@ -276,4 +304,17 @@ class Node {
|
||||
|
||||
void spriteBoxPerformedLayout() {
|
||||
}
|
||||
|
||||
// Handling user interaction
|
||||
|
||||
bool get userInteractionEnabled => _userInteractionEnabled;
|
||||
|
||||
void set userInteractionEnabled(bool userInteractionEnabled) {
|
||||
_userInteractionEnabled = userInteractionEnabled;
|
||||
if (_spriteBox != null) _spriteBox._eventTargets = null;
|
||||
}
|
||||
|
||||
bool handleEvent(SpriteBoxEvent event) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,9 @@ abstract class NodeWithSize extends Node {
|
||||
pivot = Point.origin;
|
||||
}
|
||||
|
||||
NodeWithSize.withSize(Size this.size, [Point this.pivot]);
|
||||
NodeWithSize.withSize(Size this.size, [Point this.pivot]) {
|
||||
if (pivot == null) pivot = Point.origin;
|
||||
}
|
||||
|
||||
void applyTransformForPivot(PictureRecorder canvas) {
|
||||
if (pivot.x != 0 || pivot.y != 0) {
|
||||
|
||||
@ -22,6 +22,7 @@ class Sprite extends NodeWithSize {
|
||||
double get opacity => _opacity;
|
||||
|
||||
void set opacity(double opacity) {
|
||||
assert(opacity != null);
|
||||
assert(opacity >= 0.0 && opacity <= 1.0);
|
||||
_opacity = opacity;
|
||||
}
|
||||
|
||||
@ -28,7 +28,8 @@ class SpriteBox extends RenderBox {
|
||||
|
||||
// Cached transformation matrix
|
||||
Matrix4 _transformMatrix;
|
||||
bool _transformMatrixIsDirty;
|
||||
|
||||
List<Node> _eventTargets;
|
||||
|
||||
// Setup
|
||||
|
||||
@ -47,8 +48,6 @@ class SpriteBox extends RenderBox {
|
||||
_systemWidth = width;
|
||||
_systemHeight = height;
|
||||
|
||||
_transformMatrixIsDirty = true;
|
||||
|
||||
_scheduleTick();
|
||||
}
|
||||
|
||||
@ -68,20 +67,80 @@ class SpriteBox extends RenderBox {
|
||||
|
||||
void performLayout() {
|
||||
size = constraints.constrain(Size.infinite);
|
||||
_transformMatrixIsDirty = true;
|
||||
_invalidateTransformMatrix();
|
||||
_callSpriteBoxPerformedLayout(_rootNode);
|
||||
}
|
||||
|
||||
// Event handling
|
||||
|
||||
void handleEvent(Event event, BoxHitTestEntry entry) {
|
||||
void _addEventTargets(Node node, List<Node> eventTargets) {
|
||||
if (node.userInteractionEnabled) {
|
||||
eventTargets.add(node);
|
||||
}
|
||||
for (Node child in node.children) {
|
||||
_addEventTargets(child, eventTargets);
|
||||
}
|
||||
}
|
||||
|
||||
void handleEvent(Event event, SpriteBoxHitTestEntry entry) {
|
||||
if (event is PointerEvent) {
|
||||
|
||||
if (event.type == 'pointerdown') {
|
||||
// Build list of event targets
|
||||
if (_eventTargets == null) {
|
||||
_eventTargets = [];
|
||||
_addEventTargets(_rootNode, _eventTargets);
|
||||
}
|
||||
|
||||
// Find the once that are hit by the pointer
|
||||
List<Node> nodeTargets = [];
|
||||
for (int i = _eventTargets.length - 1; i >= 0; i--) {
|
||||
Node node = _eventTargets[i];
|
||||
|
||||
// Check if the node is ready to handle a pointer
|
||||
if (node.handleMultiplePointers || node._handlingPointer == null) {
|
||||
// Do the hit test
|
||||
Point posInNodeSpace = node.convertPointToNodeSpace(entry.localPosition);
|
||||
if (node.hitTest(posInNodeSpace)) {
|
||||
nodeTargets.add(node);
|
||||
node._handlingPointer = event.pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entry.nodeTargets = nodeTargets;
|
||||
}
|
||||
|
||||
// Pass the event down to nodes that were hit by the pointerdown
|
||||
List<Node> targets = entry.nodeTargets;
|
||||
for (Node node in targets) {
|
||||
// Check if this event should be dispatched
|
||||
if (node.handleMultiplePointers || event.pointer == node._handlingPointer) {
|
||||
// Dispatch event
|
||||
bool consumedEvent = node.handleEvent(new SpriteBoxEvent(new Point(event.x, event.y), event.type, event.pointer));
|
||||
if (consumedEvent == null || consumedEvent) break;
|
||||
}
|
||||
}
|
||||
|
||||
// De-register pointer for nodes that doesn't handle multiple pointers
|
||||
for (Node node in targets) {
|
||||
if (event.type == 'pointerup' || event.type == 'pointercancel') {
|
||||
node._handlingPointer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hitTest(HitTestResult result, { Point position }) {
|
||||
result.add(new SpriteBoxHitTestEntry(this, position));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
Matrix4 get transformMatrix {
|
||||
// Get cached matrix if available
|
||||
if (!_transformMatrixIsDirty && _transformMatrix != null) {
|
||||
if (_transformMatrix != null) {
|
||||
return _transformMatrix;
|
||||
}
|
||||
|
||||
@ -145,6 +204,11 @@ class SpriteBox extends RenderBox {
|
||||
return _transformMatrix;
|
||||
}
|
||||
|
||||
void _invalidateTransformMatrix() {
|
||||
_transformMatrix = null;
|
||||
_rootNode._invalidateToBoxTransformMatrix();
|
||||
}
|
||||
|
||||
void paint(RenderObjectDisplayList canvas) {
|
||||
canvas.save();
|
||||
|
||||
@ -225,3 +289,16 @@ class SpriteBox extends RenderBox {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpriteBoxHitTestEntry extends BoxHitTestEntry {
|
||||
List<Node> nodeTargets;
|
||||
SpriteBoxHitTestEntry(RenderBox target, Point localPosition) : super(target, localPosition);
|
||||
}
|
||||
|
||||
class SpriteBoxEvent {
|
||||
Point boxPosition;
|
||||
String type;
|
||||
int pointer;
|
||||
|
||||
SpriteBoxEvent(this.boxPosition, this.type, this.pointer);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user