diff --git a/src/Container.js b/src/Container.js index a7dab538..50135e41 100644 --- a/src/Container.js +++ b/src/Container.js @@ -204,11 +204,7 @@ if (!canvas && layer) { canvas = layer.getCanvas(); - } - - if(layer && layer.getClearBeforeDraw()) { - canvas.clear(); - } + } if(this.isVisible()) { if (clip) { @@ -229,20 +225,21 @@ }, drawHit: function() { var clip = !!this.getClipFunc() && this.nodeType !== 'Stage', + dd = Kinetic.DD, hitCanvas; - if (clip) { - hitCanvas = this.getLayer().hitCanvas; - hitCanvas._clip(this); - } - if(this.isVisible() && this.isListening()) { + if(this.shouldDrawHit()) { + if (clip) { + hitCanvas = this.getLayer().hitCanvas; + hitCanvas._clip(this); + } var children = this.children, len = children.length; for(var n = 0; n < len; n++) { children[n].drawHit(); } - } - if (clip) { - hitCanvas.getContext().restore(); + if (clip) { + hitCanvas.getContext().restore(); + } } } }; diff --git a/src/DragAndDrop.js b/src/DragAndDrop.js index d90e1dc1..9c09b19e 100644 --- a/src/DragAndDrop.js +++ b/src/DragAndDrop.js @@ -1,7 +1,7 @@ (function() { Kinetic.DD = { anim: new Kinetic.Animation(), - moving: false, + started: false, offset: { x: 0, y: 0 @@ -35,9 +35,6 @@ if(!dd.moving) { dd.moving = true; - node.setListening(false); - - // execute dragstart events if defined node._handleEvent('dragstart', evt); } @@ -45,37 +42,53 @@ node._handleEvent('dragmove', evt); } }; - Kinetic.DD._endDrag = function(evt) { - var dd = Kinetic.DD, node = dd.node; + Kinetic.DD._endDragBefore = function(evt) { + var dd = Kinetic.DD, + evt = evt || {}, + node = dd.node, + nodeType, layer; if(node) { - var nodeType = node.nodeType, stage = node.getStage(); - node.setListening(true); - if(nodeType === 'Stage') { - node.draw(); + nodeType = node.nodeType, + layer = node.getLayer(); + + if((nodeType === 'Group' || nodeType === 'Shape') && node.getDragOnTop()) { + node.getStage().dragLayer.remove(); } - // else if group, shape, or layer - else { - if((nodeType === 'Group' || nodeType === 'Shape') && node.getDragOnTop()) { - node.getStage().dragLayer.remove(); - } - node.moveToTop(); - node.getLayer().draw(); - } - - delete dd.node; dd.anim.stop(); // only fire dragend event if the drag and drop - // operation actually started. This can be detected by - // checking dd.moving + // operation actually started. if(dd.moving) { dd.moving = false; - node._handleEvent('dragend', evt); + evt.dragEndNode = node; + } + + delete dd.node; + + node.moveToTop(); + + if (layer) { + layer.draw(); + } + else { + node.draw(); } } }; + Kinetic.DD._endDragAfter = function(evt) { + var evt = evt || {}, + dragEndNode = evt.dragEndNode; + + if (evt && dragEndNode) { + dragEndNode._handleEvent('dragend', evt); + } + }; + Kinetic.DD._endDrag = function() { + this._endDragBefore(); + this._endDragAfter(); + }; Kinetic.Node.prototype._startDrag = function(evt) { var dd = Kinetic.DD, that = this, @@ -99,22 +112,12 @@ // Group or Shape node types else { if(this.getDragOnTop()) { - - - // WARNING: it's important to delay the moveTo operation, - // layer redraws, and anim.start() until after the method execution - // has completed or else there will be a flicker on mobile devices - // due to the time it takes to append the dd canvas to the DOM - //setTimeout(function() { - //if(dd.node) { - // clear shape from layer canvas - that.setVisible(false); - layer.draw(); - that.setVisible(true); - stage.add(stage.dragLayer); - dd.anim.start(); - //} - //}, 0); + // clear shape from layer canvas + that.setVisible(false); + layer.draw(); + that.setVisible(true); + stage.add(stage.dragLayer); + dd.anim.start(); } else { dd.anim.start(); @@ -173,6 +176,7 @@ } } }; + Kinetic.Node.prototype._dragCleanup = function() { this.off('mousedown.kinetic'); this.off('touchstart.kinetic'); @@ -224,11 +228,15 @@ * @methodOf Kinetic.Node.prototype */ Kinetic.Node.prototype.isDraggable = Kinetic.Node.prototype.getDraggable; - + // listen for capturing phase so that the _endDrag method is // called before the stage mouseup event is triggered in order // to render the hit graph just in time to pick up the event var html = document.getElementsByTagName('html')[0]; - html.addEventListener('mouseup', Kinetic.DD._endDrag, true); - html.addEventListener('touchend', Kinetic.DD._endDrag, true); + html.addEventListener('mouseup', Kinetic.DD._endDragBefore, true); + html.addEventListener('touchend', Kinetic.DD._endDragBefore, true); + + html.addEventListener('mouseup', Kinetic.DD._endDragAfter, false); + html.addEventListener('touchend', Kinetic.DD._endDragAfter, false); + })(); diff --git a/src/Layer.js b/src/Layer.js index 3be5053c..d8fdf922 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -46,30 +46,6 @@ this.afterDrawFunc.call(this); } }, - /** - * draw children nodes on hit. this includes any groups - * or shapes - * @name drawHit - * @methodOf Kinetic.Layer.prototype - */ - drawHit: function() { - this.hitCanvas.clear(); - Kinetic.Container.prototype.drawHit.call(this); - }, - /** - * draw children nodes on scene. this includes any groups - * or shapes - * @name drawScene - * @methodOf Kinetic.Layer.prototype - * @param {Kinetic.Canvas} [canvas] - */ - drawScene: function(canvas) { - canvas = canvas || this.getCanvas(); - if(this.getClearBeforeDraw()) { - canvas.clear(); - } - Kinetic.Container.prototype.drawScene.call(this, canvas); - }, toDataURL: function(config) { config = config || {}; var mimeType = config.mimeType || null, @@ -117,6 +93,14 @@ var stage = this.getStage(); return (stage && stage._isTempDDLayerActive()) ? stage.dragLayer.canvas : this.canvas; }, + /** + * get layer hit canvas + * @name getHitCanvas + * @methodOf Kinetic.Layer.prototype + */ + getHitCanvas: function() { + return this.hitCanvas; + }, /** * get layer canvas context * @name getContext diff --git a/src/Node.js b/src/Node.js index 7e965587..41579c86 100644 --- a/src/Node.js +++ b/src/Node.js @@ -916,7 +916,7 @@ } }, /* - * draw both scene and hit graphs. + * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redra * @name draw * @methodOf Kinetic.Node.prototype * the scene renderer @@ -926,10 +926,15 @@ if(layer && layer.getClearBeforeDraw()) { layer.getCanvas().clear(); + layer.getHitCanvas().clear(); } this.drawScene(); this.drawHit(); + }, + shouldDrawHit: function() { + var dd = Kinetic.DD; + return this.isVisible() && this.isListening() && (!dd || !dd.moving); } }; diff --git a/src/Shape.js b/src/Shape.js index 5e1c6da5..2a1a85c5 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -188,7 +188,7 @@ canvas = this.getLayer().hitCanvas, context = canvas.getContext(); - if(drawFunc && this.isVisible() && this.isListening()) { + if(drawFunc && this.shouldDrawHit()) { context.save(); canvas._applyLineJoin(this); canvas._applyAncestorTransforms(this); diff --git a/src/Stage.js b/src/Stage.js index 2c3eeec1..fe67f18a 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -51,6 +51,22 @@ } this.setAttr('container', container); }, + draw: function() { + // clear children layers + var children = this.getChildren(), + len = children.length, + n, layer; + + for(n = 0; n < len; n++) { + layer = children[n]; + if (layer.getClearBeforeDraw()) { + layer.getCanvas().clear(); + layer.getHitCanvas().clear(); + } + } + + Kinetic.Node.prototype.draw.call(this); + }, /** * draw layer scene graphs * @name draw @@ -396,8 +412,10 @@ }, _mouseup: function(evt) { this._setUserPosition(evt); - var that = this, dd = Kinetic.DD, obj = this.getIntersection(this.getUserPosition()); - + var that = this, + dd = Kinetic.DD, + obj = this.getIntersection(this.getUserPosition()); + if(obj && obj.shape) { var shape = obj.shape; shape._handleEvent('mouseup', evt); diff --git a/tests/js/functionalTests.js b/tests/js/functionalTests.js index f6330095..62b5053b 100644 --- a/tests/js/functionalTests.js +++ b/tests/js/functionalTests.js @@ -38,13 +38,13 @@ Test.Modules.DD = { clientX: 291, clientY: 112 + top }); + + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 291, clientY: 112 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragAfter({dragEndNode:circle}); }, 'test dragstart, dragmove, dragend': function(containerId) { var stage = new Kinetic.Stage({ @@ -89,16 +89,17 @@ Test.Modules.DD = { var dragEnd = false; var mouseup = false; var layerDragMove = false; + var events = []; circle.on('dragstart', function() { dragStart = true; }); - /* + circle.on('dragmove', function() { dragMove = true; }); - */ + layer.on('dragmove', function() { //console.log('move'); @@ -106,13 +107,17 @@ Test.Modules.DD = { circle.on('dragend', function() { dragEnd = true; - // test set draggable false after drag end - //this.setDraggable(false); + console.log('dragend'); + events.push('dragend'); }); + + circle.on('mouseup', function() { - //console.log('mousup') + console.log('mouseup'); + events.push('mouseup'); }); + warn(layer.toDataURL() === dataUrls['drag circle before'], 'start data url is incorrect'); /* * simulate drag and drop @@ -136,23 +141,23 @@ Test.Modules.DD = { //test(dragMove, 'dragmove event was not triggered'); test(!dragEnd, 'dragend event should not have been triggered'); + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 100, clientY: 98 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); - + Kinetic.DD._endDragAfter({dragEndNode:circle}); + test(dragStart, 'dragstart event was not triggered'); - //test(dragMove, 'dragmove event was not triggered'); + test(dragMove, 'dragmove event was not triggered'); test(dragEnd, 'dragend event was not triggered'); + + test(events.toString() === 'mouseup,dragend', 'mouseup should occur before dragend'); warn(layer.toDataURL() === dataUrls['drag circle after'], 'end data url is incorrect'); - console.log(layer); + showHit(layer); - console.log(layer.eventListeners['dragmove']); }, 'cancel drag and drop by setting draggable to false': function(containerId) { var stage = new Kinetic.Stage({ @@ -210,13 +215,12 @@ Test.Modules.DD = { clientY: 100 + top }); + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 100, clientY: 100 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragAfter({dragEndNode:circle}); test(circle.getPosition().x === 380, 'circle x should be 380'); test(circle.getPosition().y === 100, 'circle y should be 100'); @@ -279,13 +283,12 @@ Test.Modules.DD = { clientY: 109 + top }); + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 210, clientY: 109 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragAfter({dragEndNode:circle2}); //console.log(layer.toDataURL()) warn(layer.toDataURL() === dataUrls['drag layer after'], 'end data url is incorrect'); @@ -315,6 +318,7 @@ Test.Modules.EVENT = { var click = false text.on('click', function() { + console.log('text click'); click = true; }); @@ -326,17 +330,18 @@ Test.Modules.EVENT = { showHit(layer); stage._mousedown({ - clientX: 291, - clientY: 112 + top + clientX: 300, + clientY: 120 + top }); - stage._mouseup({ - clientX: 291, - clientY: 112 + top - }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragBefore(); + stage._mouseup({ + clientX: 300, + clientY: 120 + top + }); + Kinetic.DD._endDragAfter({dragEndNode:text}); + + //TODO: can't get this to pass test(click, 'click event should have been fired when mousing down and then up on text'); }, @@ -544,13 +549,12 @@ Test.Modules.EVENT = { test(!mouseout, '3) mouseout should be false'); // mouseup inside circle + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 290, clientY: 100 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragAfter({dragEndNode:circle}); test(mouseover, '4) mouseover should be true'); test(mousemove, '4) mousemove should be true'); @@ -575,13 +579,12 @@ Test.Modules.EVENT = { test(!mouseout, '5) mouseout should be false'); // mouseup inside circle to trigger double click + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 290, clientY: 100 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragAfter({dragEndNode:circle}); test(mouseover, '6) mouseover should be true'); test(mousemove, '6) mousemove should be true'); @@ -638,7 +641,7 @@ Test.Modules.EVENT = { }); // end drag is tied to document mouseup and touchend event // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + //Kinetic.DD._endDrag(); test(touchstart, '9) touchstart should be true'); test(!touchmove, '9) touchmove should be false'); @@ -669,7 +672,7 @@ Test.Modules.EVENT = { }); // end drag is tied to document mouseup and touchend event // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + //Kinetic.DD._endDrag(); test(touchstart, '11) touchstart should be true'); test(!touchmove, '11) touchmove should be false'); @@ -990,13 +993,12 @@ Test.Modules.EVENT = { clientX: 374, clientY: 114 + top }); + Kinetic.DD._endDragBefore(); stage._mouseup({ clientX: 374, clientY: 114 + top }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - Kinetic.DD._endDrag(); + Kinetic.DD._endDragAfter({dragEndNode:circle}); test(e.toString() === 'circle,group1,group2,layer,stage', 'problem with event bubbling'); } @@ -1051,25 +1053,28 @@ Test.Modules['HIT FUNCS'] = { clientY: 112 + top }); - test(mouseovers === 0, 'mouseovers should be 0'); - test(mouseouts === 0, 'mouseouts should be 0'); + test(mouseovers === 0, '1) mouseovers should be 0'); + test(mouseouts === 0, '1) mouseouts should be 0'); stage._mousemove({ clientX: 286, clientY: 118 + top }); - test(mouseovers === 1, 'mouseovers should be 1'); - test(mouseouts === 0, 'mouseouts should be 0'); + test(mouseovers === 1, '2) mouseovers should be 1'); + test(mouseouts === 0, '2)mouseouts should be 0'); stage._mousemove({ clientX: 113, clientY: 112 + top }); - test(mouseovers === 1, 'mouseovers should be 1'); - test(mouseouts === 1, 'mouseouts should be 1'); + test(mouseovers === 1, '3) mouseovers should be 1'); + test(mouseouts === 1, '3) mouseouts should be 1'); + showHit(layer); + + // set drawBufferFunc with setter circle.setDrawHitFunc(function(canvas) { @@ -1081,32 +1086,34 @@ Test.Modules['HIT FUNCS'] = { canvas.stroke(this); }); + layer.getHitCanvas().clear(); layer.drawHit(); - + + // move mouse far outside circle stage._mousemove({ clientX: 113, clientY: 112 + top }); - test(mouseovers === 1, 'mouseovers should be 1'); - test(mouseouts === 1, 'mouseouts should be 1'); + test(mouseovers === 1, '4) mouseovers should be 1'); + test(mouseouts === 1, '4) mouseouts should be 1'); stage._mousemove({ clientX: 286, clientY: 118 + top }); - test(mouseovers === 1, 'mouseovers should be 1'); - test(mouseouts === 1, 'mouseouts should be 1'); + test(mouseovers === 1, '5) mouseovers should be 1'); + test(mouseouts === 1, '5) mouseouts should be 1'); stage._mousemove({ clientX: 321, clientY: 112 + top }); - test(mouseovers === 1, 'mouseovers should be 1'); - test(mouseouts === 1, 'mouseouts should be 1'); + test(mouseovers === 1, '6) mouseovers should be 1'); + test(mouseouts === 1, '6) mouseouts should be 1'); // move to center of circle stage._mousemove({ @@ -1114,7 +1121,8 @@ Test.Modules['HIT FUNCS'] = { clientY: 112 + top }); - test(mouseovers === 2, 'mouseovers should be 2'); - test(mouseouts === 1, 'mouseouts should be 1'); + test(mouseovers === 2, '7) mouseovers should be 2'); + test(mouseouts === 1, '7) mouseouts should be 1'); + } };