From 940fdfe787b2f2478bce34a365250904c7c5effb Mon Sep 17 00:00:00 2001 From: VladimirTechMan Date: Sat, 28 Jul 2018 18:43:06 -0400 Subject: [PATCH] Only update the z-indices that do change after reordering nodes Right now, when updating the z-index of a node in a container, the indices of all its siblings are refreshed. However, if the node changes its z-index from z1 to z2, we only need to refresh the z-indexes of the node's parent's children in the closed range [ min(z1,z2), max(z1,z2) ]. For groups and layers with many children that will be more optimal in many cases. In addition to the above change, this patch: * forces the parameter of Node#setZIndex() to be in the valid range. * adds the (now absent) unit tests for the methods of Node that change z-indices of the parent's children. --- src/Container.js | 10 +- src/Node.js | 30 ++++-- test/unit/Node-test.js | 205 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 14 deletions(-) diff --git a/src/Container.js b/src/Container.js index bc9ed803..7e65fcff 100644 --- a/src/Container.js +++ b/src/Container.js @@ -389,10 +389,11 @@ return arr; }, - _setChildrenIndices: function() { - this.children.each(function(child, n) { - child.index = n; - }); + _setChildrenIndicesBetween: function(idx, maxIdx) { + var children = this.children; + for (; idx <= maxIdx; ++idx) { + children[idx].index = idx; + } }, drawScene: function(can, top, caching) { var layer = this.getLayer(), @@ -681,5 +682,6 @@ * }); */ + Konva.Collection.mapMethods(Konva.Container); })(); diff --git a/src/Node.js b/src/Node.js index 9a2a60dc..2d1f54d1 100644 --- a/src/Node.js +++ b/src/Node.js @@ -609,7 +609,7 @@ if (parent && parent.children) { parent.children.splice(this.index, 1); - parent._setChildrenIndices(); + parent._setChildrenIndicesBetween(this.index, parent.children.length - 1); delete this.parent; } @@ -1089,10 +1089,11 @@ Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); return false; } + var children = this.parent.children; var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.push(this); - this.parent._setChildrenIndices(); + children.splice(index, 1); + children.push(this); + this.parent._setChildrenIndicesBetween(index, children.length - 1); return true; }, /** @@ -1111,7 +1112,7 @@ if (index < len - 1) { this.parent.children.splice(index, 1); this.parent.children.splice(index + 1, 0, this); - this.parent._setChildrenIndices(); + this.parent._setChildrenIndicesBetween(index, index + 1); return true; } return false; @@ -1131,7 +1132,7 @@ if (index > 0) { this.parent.children.splice(index, 1); this.parent.children.splice(index - 1, 0, this); - this.parent._setChildrenIndices(); + this.parent._setChildrenIndicesBetween(index - 1, index); return true; } return false; @@ -1153,7 +1154,7 @@ if (index > 0) { this.parent.children.splice(index, 1); this.parent.children.unshift(this); - this.parent._setChildrenIndices(); + this.parent._setChildrenIndicesBetween(0, index); return true; } return false; @@ -1170,10 +1171,19 @@ Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); return false; } + var children = this.parent.children; + zIndex = Math.max(0, Math.min(zIndex, children.length - 1)); + var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.splice(zIndex, 0, this); - this.parent._setChildrenIndices(); + if (zIndex !== index) { + children.splice(index, 1); + children.splice(zIndex, 0, this); + if (index < zIndex) { + this.parent._setChildrenIndicesBetween(index, zIndex); + } else { + this.parent._setChildrenIndicesBetween(zIndex, index); + } + } return this; }, /** diff --git a/test/unit/Node-test.js b/test/unit/Node-test.js index a339bd43..ef4a12e6 100644 --- a/test/unit/Node-test.js +++ b/test/unit/Node-test.js @@ -3479,3 +3479,208 @@ suite('Node', function() { assert.equal(rect.findAncestor(), null, 'return null if no selector'); }); }); + +test('moveToTop() properly changes z-indices of the node and its siblings', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect2.moveToTop(); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect2.getZIndex(), 3); + + rect1.moveToTop(); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect4.getZIndex(), 1); + assert.equal(rect2.getZIndex(), 2); + assert.equal(rect1.getZIndex(), 3); +}); + +test('moveToBottom() properly changes z-indices of the node and its siblings', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect3.moveToBottom(); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect2.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.moveToBottom(); + + assert.equal(rect4.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect1.getZIndex(), 2); + assert.equal(rect2.getZIndex(), 3); +}); + +test('moveUp() properly changes z-indices of the node and its siblings', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect1.moveUp(); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect3.moveUp(); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect3.getZIndex(), 3); +}); + +test('moveDown() properly changes z-indices of the node and its siblings', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.moveDown(); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect3.getZIndex(), 3); + + rect2.moveDown(); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect3.getZIndex(), 3); +}); + +test('setZIndex() properly changes z-indices of the node and its siblings', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect1.setZIndex(2); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect1.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect2.setZIndex(3); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect2.getZIndex(), 3); + + rect2.setZIndex(1); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect1.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.setZIndex(0); + + assert.equal(rect4.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect2.getZIndex(), 2); + assert.equal(rect1.getZIndex(), 3); +}); + +test('remove() removes the node and properly changes z-indices of its siblings', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(layer.getChildren().length, 4); + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.remove(); + + assert.equal(layer.getChildren().length, 3); + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + + rect2.remove(); + + assert.equal(layer.getChildren().length, 2); + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + + rect1.remove(); + + assert.equal(layer.getChildren().length, 1); + assert.equal(rect3.getZIndex(), 0); +});