| OLD | NEW |
| (Empty) |
| 1 part of sprites; | |
| 2 | |
| 3 double convertDegrees2Radians(double degrees) => degrees * Math.PI/180.8; | |
| 4 | |
| 5 double convertRadians2Degrees(double radians) => radians * 180.0/Math.PI; | |
| 6 | |
| 7 /// A base class for all objects that can be added to the sprite node tree and r
endered to screen using [SpriteBox] and | |
| 8 /// [SpriteWidget]. | |
| 9 /// | |
| 10 /// The [Node] class itself doesn't render any content, but provides the basic f
unctions of any type of node, such as | |
| 11 /// handling transformations and user input. To render the node tree, a root nod
e must be added to a [SpriteBox] or a | |
| 12 /// [SpriteWidget]. Commonly used sub-classes of [Node] are [Sprite], [NodeWithS
ize], and many more upcoming subclasses. | |
| 13 /// | |
| 14 /// Nodes form a hierarchical tree. Each node can have a number of children, and
the transformation (positioning, | |
| 15 /// rotation, and scaling) of a node also affects its children. | |
| 16 class Node { | |
| 17 | |
| 18 // Member variables | |
| 19 | |
| 20 SpriteBox _spriteBox; | |
| 21 Node _parent; | |
| 22 | |
| 23 Point _position = Point.origin; | |
| 24 double _rotation = 0.0; | |
| 25 | |
| 26 Matrix4 _transformMatrix = new Matrix4.identity(); | |
| 27 Matrix4 _transformMatrixNodeToBox; | |
| 28 Matrix4 _transformMatrixBoxToNode; | |
| 29 | |
| 30 double _scaleX = 1.0; | |
| 31 double _scaleY = 1.0; | |
| 32 | |
| 33 /// The visibility of this node and its children. | |
| 34 bool visible = true; | |
| 35 | |
| 36 double _zPosition = 0.0; | |
| 37 int _addedOrder; | |
| 38 int _childrenLastAddedOrder = 0; | |
| 39 bool _childrenNeedSorting = false; | |
| 40 | |
| 41 /// Decides if the node and its children is currently paused. | |
| 42 /// | |
| 43 /// A paused node will not receive any input events, update calls, or run any
animations. | |
| 44 /// | |
| 45 /// myNodeTree.paused = true; | |
| 46 bool paused = false; | |
| 47 | |
| 48 bool _userInteractionEnabled = false; | |
| 49 | |
| 50 /// If set to true the node will receive multiple pointers, otherwise it will
only receive events the first pointer. | |
| 51 /// | |
| 52 /// This property is only meaningful if [userInteractionEnabled] is set to tru
e. Default value is false. | |
| 53 /// | |
| 54 /// class MyCustomNode extends Node { | |
| 55 /// handleMultiplePointers = true; | |
| 56 /// } | |
| 57 bool handleMultiplePointers = false; | |
| 58 int _handlingPointer; | |
| 59 | |
| 60 List<Node>_children = []; | |
| 61 | |
| 62 // Constructors | |
| 63 | |
| 64 /// Creates a new [Node] without any transformation. | |
| 65 /// | |
| 66 /// var myNode = new Node(); | |
| 67 Node() { | |
| 68 } | |
| 69 | |
| 70 // Property setters and getters | |
| 71 | |
| 72 /// The [SpriteBox] this node is added to, or null if it's not currently added
to a [SpriteBox]. | |
| 73 /// | |
| 74 /// For most applications it's not necessary to access the [SpriteBox] directl
y. | |
| 75 /// | |
| 76 /// // Get the transformMode of the sprite box | |
| 77 /// var transformMode = myNode.spriteBox.transformMode; | |
| 78 SpriteBox get spriteBox => _spriteBox; | |
| 79 | |
| 80 /// The parent of this node, or null if it doesn't have a parent. | |
| 81 /// | |
| 82 /// // Hide the parent | |
| 83 /// myNode.parent.visible = false; | |
| 84 Node get parent => _parent; | |
| 85 | |
| 86 /// The rotation of this node in degrees. | |
| 87 /// | |
| 88 /// myNode.rotation = 45.0; | |
| 89 double get rotation => _rotation; | |
| 90 | |
| 91 void set rotation(double rotation) { | |
| 92 assert(rotation != null); | |
| 93 _rotation = rotation; | |
| 94 _invalidateTransformMatrix(); | |
| 95 } | |
| 96 | |
| 97 /// The position of this node relative to its parent. | |
| 98 /// | |
| 99 /// myNode.position = new Point(42.0, 42.0); | |
| 100 Point get position => _position; | |
| 101 | |
| 102 void set position(Point position) { | |
| 103 assert(position != null); | |
| 104 _position = position; | |
| 105 _invalidateTransformMatrix(); | |
| 106 } | |
| 107 | |
| 108 /// The draw order of this node compared to its parent and its siblings. | |
| 109 /// | |
| 110 /// By default nodes are drawn in the order that they have been added to a par
ent. To override this behavior the | |
| 111 /// [zPosition] property can be used. A higher value of this property will for
ce the node to be drawn in front of | |
| 112 /// siblings that have a lower value. If a negative value is used the node wil
l be drawn behind its parent. | |
| 113 /// | |
| 114 /// nodeInFront.zPosition = 1.0; | |
| 115 /// nodeBehind.zPosition = -1.0; | |
| 116 double get zPosition => _zPosition; | |
| 117 | |
| 118 void set zPosition(double zPosition) { | |
| 119 assert(zPosition != null); | |
| 120 _zPosition = zPosition; | |
| 121 if (_parent != null) { | |
| 122 _parent._childrenNeedSorting = true; | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 /// The scale of this node relative its parent. | |
| 127 /// | |
| 128 /// The [scale] property is only valid if [scaleX] and [scaleY] are equal valu
es. | |
| 129 /// | |
| 130 /// myNode.scale = 5.0; | |
| 131 double get scale { | |
| 132 assert(_scaleX == _scaleY); | |
| 133 return _scaleX; | |
| 134 } | |
| 135 | |
| 136 void set scale(double scale) { | |
| 137 assert(scale != null); | |
| 138 _scaleX = _scaleY = scale; | |
| 139 _invalidateTransformMatrix(); | |
| 140 } | |
| 141 | |
| 142 /// The horizontal scale of this node relative its parent. | |
| 143 /// | |
| 144 /// myNode.scaleX = 5.0; | |
| 145 double get scaleX => _scaleX; | |
| 146 | |
| 147 void set scaleX(double scaleX) { | |
| 148 assert(scaleX != null); | |
| 149 _scaleX = scaleX; | |
| 150 _invalidateTransformMatrix(); | |
| 151 } | |
| 152 | |
| 153 /// The vertical scale of this node relative its parent. | |
| 154 /// | |
| 155 /// myNode.scaleY = 5.0; | |
| 156 double get scaleY => _scaleY; | |
| 157 | |
| 158 void set scaleY(double scaleY) { | |
| 159 assert(scaleY != null); | |
| 160 _scaleY = scaleY; | |
| 161 _invalidateTransformMatrix(); | |
| 162 } | |
| 163 | |
| 164 /// A list of the children of this node. | |
| 165 /// | |
| 166 /// This list should only be modified by using the [addChild] and [removeChild
] methods. | |
| 167 /// | |
| 168 /// // Iterate over a nodes children | |
| 169 /// for (Node child in myNode.children) { | |
| 170 /// // Do something with the child | |
| 171 /// } | |
| 172 List<Node> get children { | |
| 173 _sortChildren(); | |
| 174 return _children; | |
| 175 } | |
| 176 | |
| 177 // Adding and removing children | |
| 178 | |
| 179 /// Adds a child to this node. | |
| 180 /// | |
| 181 /// The same node cannot be added to multiple nodes. | |
| 182 /// | |
| 183 /// addChild(new Sprite(myImage)); | |
| 184 void addChild(Node child) { | |
| 185 assert(child != null); | |
| 186 assert(child._parent == null); | |
| 187 | |
| 188 _childrenNeedSorting = true; | |
| 189 _children.add(child); | |
| 190 child._parent = this; | |
| 191 child._spriteBox = this._spriteBox; | |
| 192 _childrenLastAddedOrder += 1; | |
| 193 child._addedOrder = _childrenLastAddedOrder; | |
| 194 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
| 195 } | |
| 196 | |
| 197 /// Removes a child from this node. | |
| 198 /// | |
| 199 /// removeChild(myChildNode); | |
| 200 void removeChild(Node child) { | |
| 201 assert(child != null); | |
| 202 if (_children.remove(child)) { | |
| 203 child._parent = null; | |
| 204 child._spriteBox = null; | |
| 205 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 /// Removes this node from its parent node. | |
| 210 /// | |
| 211 /// removeFromParent(); | |
| 212 void removeFromParent() { | |
| 213 assert(_parent != null); | |
| 214 _parent.removeChild(this); | |
| 215 } | |
| 216 | |
| 217 /// Removes all children of this node. | |
| 218 /// | |
| 219 /// removeAllChildren(); | |
| 220 void removeAllChildren() { | |
| 221 for (Node child in _children) { | |
| 222 child._parent = null; | |
| 223 child._spriteBox = null; | |
| 224 } | |
| 225 _children = []; | |
| 226 _childrenNeedSorting = false; | |
| 227 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
| 228 } | |
| 229 | |
| 230 void _sortChildren() { | |
| 231 // Sort children primarily by zPosition, secondarily by added order | |
| 232 if (_childrenNeedSorting) { | |
| 233 _children.sort((Node a, Node b) { | |
| 234 if (a._zPosition == b._zPosition) { | |
| 235 return a._addedOrder - b._addedOrder; | |
| 236 } | |
| 237 else if (a._zPosition > b._zPosition) { | |
| 238 return 1; | |
| 239 } | |
| 240 else { | |
| 241 return -1; | |
| 242 } | |
| 243 }); | |
| 244 _childrenNeedSorting = false; | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 // Calculating the transformation matrix | |
| 249 | |
| 250 /// The transformMatrix describes the transformation from the node's parent. | |
| 251 /// | |
| 252 /// You cannot set the transformMatrix directly, instead use the position, rot
ation and scale properties. | |
| 253 /// | |
| 254 /// Matrix4 matrix = myNode.transformMatrix; | |
| 255 Matrix4 get transformMatrix { | |
| 256 if (_transformMatrix != null) { | |
| 257 return _transformMatrix; | |
| 258 } | |
| 259 | |
| 260 double cx, sx, cy, sy; | |
| 261 | |
| 262 if (_rotation == 0.0) { | |
| 263 cx = 1.0; | |
| 264 sx = 0.0; | |
| 265 cy = 1.0; | |
| 266 sy = 0.0; | |
| 267 } | |
| 268 else { | |
| 269 double radiansX = convertDegrees2Radians(_rotation); | |
| 270 double radiansY = convertDegrees2Radians(_rotation); | |
| 271 | |
| 272 cx = Math.cos(radiansX); | |
| 273 sx = Math.sin(radiansX); | |
| 274 cy = Math.cos(radiansY); | |
| 275 sy = Math.sin(radiansY); | |
| 276 } | |
| 277 | |
| 278 // Create transformation matrix for scale, position and rotation | |
| 279 _transformMatrix = new Matrix4(cy * _scaleX, sy * _scaleX, 0.0, 0.0, | |
| 280 -sx * _scaleY, cx * _scaleY, 0.0, 0.0, | |
| 281 0.0, 0.0, 1.0, 0.0, | |
| 282 _position.x, _position.y, 0.0, 1.0); | |
| 283 | |
| 284 return _transformMatrix; | |
| 285 } | |
| 286 | |
| 287 void _invalidateTransformMatrix() { | |
| 288 _transformMatrix = null; | |
| 289 _invalidateToBoxTransformMatrix(); | |
| 290 } | |
| 291 | |
| 292 void _invalidateToBoxTransformMatrix () { | |
| 293 _transformMatrixNodeToBox = null; | |
| 294 _transformMatrixBoxToNode = null; | |
| 295 | |
| 296 for (Node child in children) { | |
| 297 child._invalidateToBoxTransformMatrix(); | |
| 298 } | |
| 299 } | |
| 300 | |
| 301 // Transforms to other nodes | |
| 302 | |
| 303 Matrix4 _nodeToBoxMatrix() { | |
| 304 assert(_spriteBox != null); | |
| 305 if (_transformMatrixNodeToBox != null) { | |
| 306 return _transformMatrixNodeToBox; | |
| 307 } | |
| 308 | |
| 309 if (_parent == null) { | |
| 310 // Base case, we are at the top | |
| 311 assert(this == _spriteBox.rootNode); | |
| 312 _transformMatrixNodeToBox = new Matrix4.copy(_spriteBox.transformMatrix).m
ultiply(transformMatrix); | |
| 313 } | |
| 314 else { | |
| 315 _transformMatrixNodeToBox = new Matrix4.copy(_parent._nodeToBoxMatrix()).m
ultiply(transformMatrix); | |
| 316 } | |
| 317 return _transformMatrixNodeToBox; | |
| 318 } | |
| 319 | |
| 320 Matrix4 _boxToNodeMatrix() { | |
| 321 assert(_spriteBox != null); | |
| 322 | |
| 323 if (_transformMatrixBoxToNode != null) { | |
| 324 return _transformMatrixBoxToNode; | |
| 325 } | |
| 326 | |
| 327 _transformMatrixBoxToNode = new Matrix4.copy(_nodeToBoxMatrix()); | |
| 328 _transformMatrixBoxToNode.invert(); | |
| 329 | |
| 330 return _transformMatrixBoxToNode; | |
| 331 } | |
| 332 | |
| 333 /// Converts a point from the coordinate system of the [SpriteBox] to the loca
l coordinate system of the node. | |
| 334 /// | |
| 335 /// This method is particularly useful when handling pointer events and need t
he pointers position in a local | |
| 336 /// coordinate space. | |
| 337 /// | |
| 338 /// Point localPoint = myNode.convertPointToNodeSpace(pointInBoxCoordinate
s); | |
| 339 Point convertPointToNodeSpace(Point boxPoint) { | |
| 340 assert(boxPoint != null); | |
| 341 assert(_spriteBox != null); | |
| 342 | |
| 343 Vector4 v =_boxToNodeMatrix().transform(new Vector4(boxPoint.x, boxPoint.y,
0.0, 1.0)); | |
| 344 return new Point(v[0], v[1]); | |
| 345 } | |
| 346 | |
| 347 /// Converts a point from the local coordinate system of the node to the coord
inate system of the [SpriteBox]. | |
| 348 /// | |
| 349 /// Point pointInBoxCoordinates = myNode.convertPointToBoxSpace(localPoint
); | |
| 350 Point convertPointToBoxSpace(Point nodePoint) { | |
| 351 assert(nodePoint != null); | |
| 352 assert(_spriteBox != null); | |
| 353 | |
| 354 Vector4 v =_nodeToBoxMatrix().transform(new Vector4(nodePoint.x, nodePoint.y
, 0.0, 1.0)); | |
| 355 return new Point(v[0], v[1]); | |
| 356 } | |
| 357 | |
| 358 /// Converts a [point] from another [node]s coordinate system into the local c
oordinate system of this node. | |
| 359 /// | |
| 360 /// Point pointInNodeASpace = nodeA.convertPointFromNode(pointInNodeBSpace
, nodeB); | |
| 361 Point convertPointFromNode(Point point, Node node) { | |
| 362 assert(node != null); | |
| 363 assert(point != null); | |
| 364 assert(_spriteBox != null); | |
| 365 assert(_spriteBox == node._spriteBox); | |
| 366 | |
| 367 Point boxPoint = node.convertPointToBoxSpace(point); | |
| 368 Point localPoint = convertPointToNodeSpace(boxPoint); | |
| 369 | |
| 370 return localPoint; | |
| 371 } | |
| 372 | |
| 373 // Hit test | |
| 374 | |
| 375 /// Returns true if the [point] is inside the node, the [point] is in the loca
l coordinate system of the node. | |
| 376 /// | |
| 377 /// myNode.isPointInside(localPoint); | |
| 378 /// | |
| 379 /// [NodeWithSize] provides a basic bounding box check for this method, if you
require a more detailed check this | |
| 380 /// method can be overridden. | |
| 381 /// | |
| 382 /// bool isPointInside (Point nodePoint) { | |
| 383 /// double minX = -size.width * pivot.x; | |
| 384 /// double minY = -size.height * pivot.y; | |
| 385 /// double maxX = minX + size.width; | |
| 386 /// double maxY = minY + size.height; | |
| 387 /// return (nodePoint.x >= minX && nodePoint.x < maxX && | |
| 388 /// nodePoint.y >= minY && nodePoint.y < maxY); | |
| 389 /// } | |
| 390 bool isPointInside(Point point) { | |
| 391 assert(point != null); | |
| 392 | |
| 393 return false; | |
| 394 } | |
| 395 | |
| 396 // Rendering | |
| 397 | |
| 398 void _visit(RenderCanvas canvas) { | |
| 399 assert(canvas != null); | |
| 400 if (!visible) return; | |
| 401 | |
| 402 _prePaint(canvas); | |
| 403 _visitChildren(canvas); | |
| 404 _postPaint(canvas); | |
| 405 } | |
| 406 | |
| 407 void _prePaint(RenderCanvas canvas) { | |
| 408 canvas.save(); | |
| 409 | |
| 410 // Get the transformation matrix and apply transform | |
| 411 canvas.concat(transformMatrix.storage); | |
| 412 } | |
| 413 | |
| 414 /// Paints this node to the canvas. | |
| 415 /// | |
| 416 /// Subclasses, such as [Sprite], override this method to do the actual painti
ng of the node. To do custom | |
| 417 /// drawing override this method and make calls to the [canvas] object. All dr
awing is done in the node's local | |
| 418 /// coordinate system, relative to the node's position. If you want to make th
e drawing relative to the node's | |
| 419 /// bounding box's origin, override [NodeWithSize] and call the applyTransform
ForPivot method before making calls for | |
| 420 /// drawing. | |
| 421 /// | |
| 422 /// void paint(RenderCanvas canvas) { | |
| 423 /// canvas.save(); | |
| 424 /// applyTransformForPivot(canvas); | |
| 425 /// | |
| 426 /// // Do painting here | |
| 427 /// | |
| 428 /// canvas.restore(); | |
| 429 /// } | |
| 430 void paint(RenderCanvas canvas) { | |
| 431 } | |
| 432 | |
| 433 void _visitChildren(RenderCanvas canvas) { | |
| 434 // Sort children if needed | |
| 435 _sortChildren(); | |
| 436 | |
| 437 int i = 0; | |
| 438 | |
| 439 // Visit children behind this node | |
| 440 while (i < _children.length) { | |
| 441 Node child = _children[i]; | |
| 442 if (child.zPosition >= 0.0) break; | |
| 443 child._visit(canvas); | |
| 444 i++; | |
| 445 } | |
| 446 | |
| 447 // Paint this node | |
| 448 paint(canvas); | |
| 449 | |
| 450 // Visit children in front of this node | |
| 451 while (i < _children.length) { | |
| 452 Node child = _children[i]; | |
| 453 child._visit(canvas); | |
| 454 i++; | |
| 455 } | |
| 456 } | |
| 457 | |
| 458 void _postPaint(RenderCanvas canvas) { | |
| 459 canvas.restore(); | |
| 460 } | |
| 461 | |
| 462 // Receiving update calls | |
| 463 | |
| 464 /// Called before a frame is drawn. | |
| 465 /// | |
| 466 /// Override this method to do any updates to the node or node tree before it'
s drawn to screen. | |
| 467 /// | |
| 468 /// // Make the node rotate at a fixed speed | |
| 469 /// void update(double dt) { | |
| 470 /// rotation = rotation * 10.0 * dt; | |
| 471 /// } | |
| 472 void update(double dt) { | |
| 473 } | |
| 474 | |
| 475 /// Called whenever the [SpriteBox] is modified or resized, or if the device i
s rotated. | |
| 476 /// | |
| 477 /// Override this method to do any updates that may be necessary to correctly
display the node or node tree with the | |
| 478 /// new layout of the [SpriteBox]. | |
| 479 /// | |
| 480 /// void spriteBoxPerformedLayout() { | |
| 481 /// // Move some stuff around here | |
| 482 /// } | |
| 483 void spriteBoxPerformedLayout() { | |
| 484 } | |
| 485 | |
| 486 // Handling user interaction | |
| 487 | |
| 488 /// The node will receive user interactions, such as pointer (touch or mouse)
events. | |
| 489 /// | |
| 490 /// class MyCustomNode extends NodeWithSize { | |
| 491 /// userInteractionEnabled = true; | |
| 492 /// } | |
| 493 bool get userInteractionEnabled => _userInteractionEnabled; | |
| 494 | |
| 495 void set userInteractionEnabled(bool userInteractionEnabled) { | |
| 496 _userInteractionEnabled = userInteractionEnabled; | |
| 497 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
| 498 } | |
| 499 | |
| 500 /// Handles an event, such as a pointer (touch or mouse) event. | |
| 501 /// | |
| 502 /// Override this method to handle events. The node will only receive events i
f the [userInteractionEnabled] property | |
| 503 /// is set to true and the [isPointInside] method returns true for the positio
n of the pointer down event (default | |
| 504 /// behavior provided by [NodeWithSize]). Unless [handleMultiplePointers] is s
et to true, the node will only receive | |
| 505 /// events for the first pointer that is down. | |
| 506 /// | |
| 507 /// Return true if the node has consumed the event, if an event is consumed it
will not be passed on to nodes behind | |
| 508 /// the current node. | |
| 509 /// | |
| 510 /// // MyTouchySprite gets transparent when we touch it | |
| 511 /// class MyTouchySprite extends Sprite { | |
| 512 /// | |
| 513 /// MyTouchySprite(Image img) : super (img) { | |
| 514 /// userInteractionEnabled = true; | |
| 515 /// } | |
| 516 /// | |
| 517 /// bool handleEvent(SpriteBoxEvent event) { | |
| 518 /// if (event.type == 'pointerdown) { | |
| 519 /// opacity = 0.5; | |
| 520 /// } | |
| 521 /// else if (event.type == 'pointerup') { | |
| 522 /// opacity = 1.0; | |
| 523 /// } | |
| 524 /// return true; | |
| 525 /// } | |
| 526 /// } | |
| 527 bool handleEvent(SpriteBoxEvent event) { | |
| 528 return false; | |
| 529 } | |
| 530 } | |
| OLD | NEW |