diff --git a/CHANGELOG.md b/CHANGELOG.md index 3833468e..3c89d4e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Not released: * Typescript fixes +* Experimental pointer events support. Do `Konva._pointerEventsEnabled = true;` to enable ## [3.2.6][2019-05-09] diff --git a/konva.js b/konva.js index 3aa457f6..061113a2 100644 --- a/konva.js +++ b/konva.js @@ -83,6 +83,7 @@ return Konva.angleDeg ? angle * PI_OVER_180 : angle; }, enableTrace: false, + _pointerEventsEnabled: false, // TODO: move that to stage? listenClickTap: false, inDblClickWindow: false, @@ -5423,8 +5424,40 @@ */ Collection.mapMethods(Container); + var Captures = new Map(); + function getCapturedShape(pointerId) { + return Captures.get(pointerId); + } + function createEvent(evt) { + return { + evt: evt, + pointerId: evt.pointerId + }; + } + function hasPointerCapture(pointerId, shape) { + return Captures.get(pointerId) === shape; + } + function setPointerCapture(pointerId, shape) { + releaseCapture(pointerId); + var content = shape.getStage().content; + content.setPointerCapture(pointerId); + Captures.set(pointerId, shape); + shape._fire('gotpointercapture', createEvent(new PointerEvent('gotpointercapture'))); + } + function releaseCapture(pointerId, target) { + var shape = Captures.get(pointerId); + if (!shape) + return; + var content = shape.getStage().content; + content.releasePointerCapture(pointerId); + Captures.delete(pointerId); + shape._fire('lostpointercapture', createEvent(new PointerEvent('lostpointercapture'))); + } + // CONSTANTS - var STAGE$1 = 'Stage', STRING = 'string', PX = 'px', MOUSEOUT = 'mouseout', MOUSELEAVE$1 = 'mouseleave', MOUSEOVER = 'mouseover', MOUSEENTER$1 = 'mouseenter', MOUSEMOVE = 'mousemove', MOUSEDOWN = 'mousedown', MOUSEUP = 'mouseup', CONTEXTMENU = 'contextmenu', CLICK = 'click', DBL_CLICK = 'dblclick', TOUCHSTART = 'touchstart', TOUCHEND = 'touchend', TAP = 'tap', DBL_TAP = 'dbltap', TOUCHMOVE = 'touchmove', WHEEL = 'wheel', CONTENT_MOUSEOUT = 'contentMouseout', CONTENT_MOUSEOVER = 'contentMouseover', CONTENT_MOUSEMOVE = 'contentMousemove', CONTENT_MOUSEDOWN = 'contentMousedown', CONTENT_MOUSEUP = 'contentMouseup', CONTENT_CONTEXTMENU = 'contentContextmenu', CONTENT_CLICK = 'contentClick', CONTENT_DBL_CLICK = 'contentDblclick', CONTENT_TOUCHSTART = 'contentTouchstart', CONTENT_TOUCHEND = 'contentTouchend', CONTENT_DBL_TAP = 'contentDbltap', CONTENT_TAP = 'contentTap', CONTENT_TOUCHMOVE = 'contentTouchmove', CONTENT_WHEEL = 'contentWheel', RELATIVE = 'relative', KONVA_CONTENT = 'konvajs-content', UNDERSCORE = '_', CONTAINER = 'container', MAX_LAYERS_NUMBER = 5, EMPTY_STRING$1 = '', EVENTS = [ + var STAGE$1 = 'Stage', STRING = 'string', PX = 'px', MOUSEOUT = 'mouseout', MOUSELEAVE$1 = 'mouseleave', MOUSEOVER = 'mouseover', MOUSEENTER$1 = 'mouseenter', MOUSEMOVE = 'mousemove', MOUSEDOWN = 'mousedown', MOUSEUP = 'mouseup', + // TODO: add them into "on" method docs and into site docs + POINTERMOVE = 'pointermove', POINTERDOWN = 'pointerdown', POINTERUP = 'pointerup', POINTERCANCEL = 'pointercancel', LOSTPOINTERCAPTURE = 'lostpointercapture', CONTEXTMENU = 'contextmenu', CLICK = 'click', DBL_CLICK = 'dblclick', TOUCHSTART = 'touchstart', TOUCHEND = 'touchend', TAP = 'tap', DBL_TAP = 'dbltap', TOUCHMOVE = 'touchmove', WHEEL = 'wheel', CONTENT_MOUSEOUT = 'contentMouseout', CONTENT_MOUSEOVER = 'contentMouseover', CONTENT_MOUSEMOVE = 'contentMousemove', CONTENT_MOUSEDOWN = 'contentMousedown', CONTENT_MOUSEUP = 'contentMouseup', CONTENT_CONTEXTMENU = 'contentContextmenu', CONTENT_CLICK = 'contentClick', CONTENT_DBL_CLICK = 'contentDblclick', CONTENT_TOUCHSTART = 'contentTouchstart', CONTENT_TOUCHEND = 'contentTouchend', CONTENT_DBL_TAP = 'contentDbltap', CONTENT_TAP = 'contentTap', CONTENT_TOUCHMOVE = 'contentTouchmove', CONTENT_WHEEL = 'contentWheel', RELATIVE = 'relative', KONVA_CONTENT = 'konvajs-content', UNDERSCORE = '_', CONTAINER = 'container', MAX_LAYERS_NUMBER = 5, EMPTY_STRING$1 = '', EVENTS = [ MOUSEENTER$1, MOUSEDOWN, MOUSEMOVE, @@ -5435,7 +5468,12 @@ TOUCHEND, MOUSEOVER, WHEEL, - CONTEXTMENU + CONTEXTMENU, + POINTERDOWN, + POINTERMOVE, + POINTERUP, + POINTERCANCEL, + LOSTPOINTERCAPTURE ], // cached variables eventsLength = EVENTS.length; @@ -6013,6 +6051,55 @@ } this._fire(CONTENT_WHEEL, { evt: evt }); }; + Stage.prototype._pointerdown = function (evt) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + var shape = getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + if (shape) { + shape._fireAndBubble(POINTERDOWN, createEvent(evt)); + } + }; + Stage.prototype._pointermove = function (evt) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + var shape = getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + if (shape) { + shape._fireAndBubble(POINTERMOVE, createEvent(evt)); + } + }; + Stage.prototype._pointerup = function (evt) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + var shape = getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + if (shape) { + shape._fireAndBubble(POINTERUP, createEvent(evt)); + } + releaseCapture(evt.pointerId); + }; + Stage.prototype._pointercancel = function (evt) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + var shape = getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + if (shape) { + shape._fireAndBubble(POINTERUP, createEvent(evt)); + } + releaseCapture(evt.pointerId); + }; + Stage.prototype._lostpointercapture = function (evt) { + releaseCapture(evt.pointerId); + }; /** * manually register pointers positions (mouse/touch) in the stage. * So you can use stage.getPointerPosition(). Usually you don't need to use that method @@ -6733,7 +6820,7 @@ // it give better result when a shape has // stroke with fill and with some opacity Shape.prototype._useBufferCanvas = function (caching) { - return ((!caching || this.hasShadow()) && + return !!((!caching || this.hasShadow()) && this.perfectDrawEnabled() && this.getAbsoluteOpacity() !== 1 && this.hasFill() && @@ -6993,6 +7080,15 @@ } return this; }; + Shape.prototype.hasPointerCapture = function (pointerId) { + return hasPointerCapture(pointerId, this); + }; + Shape.prototype.setPointerCapture = function (pointerId) { + setPointerCapture(pointerId, this); + }; + Shape.prototype.releaseCapture = function (pointerId) { + releaseCapture(pointerId, this); + }; return Shape; }(Node)); Shape.prototype._fillFunc = _fillFunc; @@ -10159,7 +10255,7 @@ return _super !== null && _super.apply(this, arguments) || this; } Image.prototype._useBufferCanvas = function () { - return ((this.hasShadow() || this.getAbsoluteOpacity() !== 1) && + return !!((this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke() && this.getStage()); }; diff --git a/konva.min.js b/konva.min.js index cc1f2603..b2eeebd5 100644 --- a/konva.min.js +++ b/konva.min.js @@ -9,4 +9,4 @@ * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) * * @license - */var e=Math.PI/180;var t=function(t){var e=t.toLowerCase(),i=/(chrome)[ /]([\w.]+)/.exec(e)||/(webkit)[ /]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ /]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[],n=!!t.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i),r=!!t.match(/IEMobile/i);return{browser:i[1]||"",version:i[2]||"0",isIE:function(t){var e=t.indexOf("msie ");if(0>16&255,g:e>>8&255,b:255&e}},getRandomColor:function(){for(var t=(16777215*Math.random()<<0).toString(16);t.length<6;)t="0"+t;return"#"+t},get:function(t,e){return void 0===t?e:t},getRGB:function(t){var e;return t in l?{r:(e=l[t])[0],g:e[1],b:e[2]}:"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=d.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:function(t){return t=t||"black",O._namedColorToRBA(t)||O._hex3ColorToRGBA(t)||O._hex6ColorToRGBA(t)||O._rgbColorToRGBA(t)||O._rgbaColorToRGBA(t)},_namedColorToRBA:function(t){var e=l[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA:function(t){if(0===t.indexOf("rgb(")){var e=(t=t.match(/rgb\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA:function(t){if(0===t.indexOf("rgba(")){var e=(t=t.match(/rgba\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex6ColorToRGBA:function(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex3ColorToRGBA:function(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},haveIntersection:function(t,e){return!(e.x>t.x+t.width||e.x+e.widtht.y+t.height||e.y+e.heighte.length){var o=e;e=t,t=o}for(n=0;n=this.parent.children.length)&&O.warn("Unexpected value "+t+" for zIndex property. zIndex is just index of a node in children of its parent. Expected value is from 0 to "+(this.parent.children.length-1)+".");var e=this.index;return this.parent.children.splice(e,1),this.parent.children.splice(t,0,this),this.parent._setChildrenIndices(),this},s.prototype.getAbsoluteOpacity=function(){return this._getCache(N,this._getAbsoluteOpacity)},s.prototype._getAbsoluteOpacity=function(){var t=this.opacity(),e=this.getParent();return e&&!e._isUnderCache&&(t*=this.getParent().getAbsoluteOpacity()),t},s.prototype.moveTo=function(t){return this.getParent()!==t&&(this._remove(),t.add(this)),this},s.prototype.toObject=function(){var t,e,i,n={},r=this.getAttrs();for(t in n.attrs={},r)e=r[t],O.isObject(e)&&!O._isPlainObject(e)&&!O._isArray(e)||(i="function"==typeof this[t]&&this[t],delete r[t],(i?i.call(this):null)!==(r[t]=e)&&(n.attrs[t]=e));return n.className=this.getClassName(),O._prepareToStringify(n)},s.prototype.toJSON=function(){return JSON.stringify(this.toObject())},s.prototype.getParent=function(){return this.parent},s.prototype.findAncestors=function(t,e,i){var n=[];e&&this._isMatch(t)&&n.push(this);for(var r=this.parent;r;){if(r===i)return n;r._isMatch(t)&&n.push(r),r=r.parent}return n},s.prototype.isAncestorOf=function(t){return!1},s.prototype.findAncestor=function(t,e,i){return this.findAncestors(t,e,i)[0]},s.prototype._isMatch=function(t){if(!t)return!1;if("function"==typeof t)return t(this);var e,i,n=t.replace(/ /g,"").split(","),r=n.length;for(e=0;ethis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())},t.prototype.getTime=function(){return this._time},t.prototype.setPosition=function(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t},t.prototype.getPosition=function(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)},t.prototype.play=function(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},t.prototype.reverse=function(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},t.prototype.seek=function(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")},t.prototype.reset=function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},t.prototype.finish=function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},t.prototype.update=function(){this.setPosition(this.getPosition(this._time))},t.prototype.onEnterFrame=function(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)},t.prototype.pause=function(){this.state=1,this.fire("onPause")},t.prototype.getTimer=function(){return(new Date).getTime()},t}(),qt=function(){function u(t){var e,i,n=this,r=t.node,a=r._id,o=t.easing||Vt.Linear,s=!!t.yoyo;e=void 0===t.duration?.3:0===t.duration?.001:t.duration,this.node=r,this._id=Xt++;var h=r.getLayer()||(r instanceof L.Stage?r.getLayers():null);for(i in h||O.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new I(function(){n.tween.onEnterFrame()},h),this.tween=new Ut(i,function(t){n._tweenFunc(t)},o,0,1,1e3*e,s),this._addListeners(),u.attrs[a]||(u.attrs[a]={}),u.attrs[a][this._id]||(u.attrs[a][this._id]={}),u.tweens[a]||(u.tweens[a]={}),t)void 0===Yt[i]&&this._addAttr(i,t[i]);this.reset(),this.onFinish=t.onFinish,this.onReset=t.onReset}return u.prototype._addAttr=function(t,e){var i,n,r,a,o,s,h,l,c=this.node,d=c._id;if((r=u.tweens[d][t])&&delete u.attrs[d][r][t],i=c.getAttr(t),O._isArray(e))if(n=[],o=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(h=i,i=O._prepareArrayForTween(i,e,c.closed())):(s=e,e=O._prepareArrayForTween(e,i,c.closed()))),0===t.indexOf("fill"))for(a=0;athis.dataArray[i].pathLength;)t-=this.dataArray[i].pathLength,++i;if(i===n)return{x:(e=this.dataArray[i-1].points.slice(-2))[0],y:e[1]};if(t<.01)return{x:(e=this.dataArray[i].points.slice(0,2))[0],y:e[1]};var r=this.dataArray[i],a=r.points;switch(r.command){case"L":return u.getPointOnLine(t,r.start.x,r.start.y,a[0],a[1]);case"C":return u.getPointOnCubicBezier(t/r.pathLength,r.start.x,r.start.y,a[0],a[1],a[2],a[3],a[4],a[5]);case"Q":return u.getPointOnQuadraticBezier(t/r.pathLength,r.start.x,r.start.y,a[0],a[1],a[2],a[3]);case"A":var o=a[0],s=a[1],h=a[2],l=a[3],c=a[4],d=a[5],p=a[6];return c+=d*t/r.pathLength,u.getPointOnEllipticalArc(o,s,h,l,c,p)}return null},u.getLineLength=function(t,e,i,n){return Math.sqrt((i-t)*(i-t)+(n-e)*(n-e))},u.getPointOnLine=function(t,e,i,n,r,a,o){void 0===a&&(a=e),void 0===o&&(o=i);var s=(r-i)/(n-e+1e-8),h=Math.sqrt(t*t/(1+s*s));n>>1,P=_.slice(0,1+k),T=this._getTextWidth(P)+v;T<=l?(b=1+k,w=P+(g?"…":""),C=T):x=k}if(!w)break;if(f){var M,A=_[w.length];0<(M=(" "===A||"-"===A)&&C<=l?w.length:Math.max(w.lastIndexOf(" "),w.lastIndexOf("-"))+1)&&(b=M,w=w.slice(0,b),C=this._getTextWidth(w))}if(w=w.trimRight(),this._addTextLine(w),i=Math.max(i,C),d+=n,!u||s&&ce?g=le.getPointOnLine(e,f.x,f.y,v.points[0],v.points[1],f.x,f.y):v=void 0;break;case"A":var o=v.points[4],s=v.points[5],h=v.points[4]+s;0===m?m=o+1e-8:iv.pathLength?1e-8:e/v.pathLength:i>W,0!==C?(C=255/C,P[s]=(l*B>>W)*C,P[s+1]=(c*B>>W)*C,P[s+2]=(d*B>>W)*C):P[s]=P[s+1]=P[s+2]=0,l-=u,c-=f,d-=g,p-=v,u-=E.r,f-=E.g,g-=E.b,v-=E.a,a=h+((a=i+e+1)>W,0>W)*C,P[a+1]=(c*B>>W)*C,P[a+2]=(d*B>>W)*C):P[a]=P[a+1]=P[a+2]=0,l-=u,c-=f,d-=g,p-=v,u-=E.r,f-=E.g,g-=E.b,v-=E.a,a=i+((a=n+L)>16&255,g:e>>8&255,b:255&e}},getRandomColor:function(){for(var t=(16777215*Math.random()<<0).toString(16);t.length<6;)t="0"+t;return"#"+t},get:function(t,e){return void 0===t?e:t},getRGB:function(t){var e;return t in l?{r:(e=l[t])[0],g:e[1],b:e[2]}:"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=d.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:function(t){return t=t||"black",O._namedColorToRBA(t)||O._hex3ColorToRGBA(t)||O._hex6ColorToRGBA(t)||O._rgbColorToRGBA(t)||O._rgbaColorToRGBA(t)},_namedColorToRBA:function(t){var e=l[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA:function(t){if(0===t.indexOf("rgb(")){var e=(t=t.match(/rgb\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA:function(t){if(0===t.indexOf("rgba(")){var e=(t=t.match(/rgba\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex6ColorToRGBA:function(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex3ColorToRGBA:function(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},haveIntersection:function(t,e){return!(e.x>t.x+t.width||e.x+e.widtht.y+t.height||e.y+e.heighte.length){var a=e;e=t,t=a}for(n=0;n=this.parent.children.length)&&O.warn("Unexpected value "+t+" for zIndex property. zIndex is just index of a node in children of its parent. Expected value is from 0 to "+(this.parent.children.length-1)+".");var e=this.index;return this.parent.children.splice(e,1),this.parent.children.splice(t,0,this),this.parent._setChildrenIndices(),this},s.prototype.getAbsoluteOpacity=function(){return this._getCache(N,this._getAbsoluteOpacity)},s.prototype._getAbsoluteOpacity=function(){var t=this.opacity(),e=this.getParent();return e&&!e._isUnderCache&&(t*=this.getParent().getAbsoluteOpacity()),t},s.prototype.moveTo=function(t){return this.getParent()!==t&&(this._remove(),t.add(this)),this},s.prototype.toObject=function(){var t,e,i,n={},r=this.getAttrs();for(t in n.attrs={},r)e=r[t],O.isObject(e)&&!O._isPlainObject(e)&&!O._isArray(e)||(i="function"==typeof this[t]&&this[t],delete r[t],(i?i.call(this):null)!==(r[t]=e)&&(n.attrs[t]=e));return n.className=this.getClassName(),O._prepareToStringify(n)},s.prototype.toJSON=function(){return JSON.stringify(this.toObject())},s.prototype.getParent=function(){return this.parent},s.prototype.findAncestors=function(t,e,i){var n=[];e&&this._isMatch(t)&&n.push(this);for(var r=this.parent;r;){if(r===i)return n;r._isMatch(t)&&n.push(r),r=r.parent}return n},s.prototype.isAncestorOf=function(t){return!1},s.prototype.findAncestor=function(t,e,i){return this.findAncestors(t,e,i)[0]},s.prototype._isMatch=function(t){if(!t)return!1;if("function"==typeof t)return t(this);var e,i,n=t.replace(/ /g,"").split(","),r=n.length;for(e=0;ethis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())},t.prototype.getTime=function(){return this._time},t.prototype.setPosition=function(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t},t.prototype.getPosition=function(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)},t.prototype.play=function(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},t.prototype.reverse=function(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},t.prototype.seek=function(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")},t.prototype.reset=function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},t.prototype.finish=function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},t.prototype.update=function(){this.setPosition(this.getPosition(this._time))},t.prototype.onEnterFrame=function(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)},t.prototype.pause=function(){this.state=1,this.fire("onPause")},t.prototype.getTimer=function(){return(new Date).getTime()},t}(),te=function(){function u(t){var e,i,n=this,r=t.node,o=r._id,a=t.easing||ee.Linear,s=!!t.yoyo;e=void 0===t.duration?.3:0===t.duration?.001:t.duration,this.node=r,this._id=Jt++;var h=r.getLayer()||(r instanceof L.Stage?r.getLayers():null);for(i in h||O.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new I(function(){n.tween.onEnterFrame()},h),this.tween=new $t(i,function(t){n._tweenFunc(t)},a,0,1,1e3*e,s),this._addListeners(),u.attrs[o]||(u.attrs[o]={}),u.attrs[o][this._id]||(u.attrs[o][this._id]={}),u.tweens[o]||(u.tweens[o]={}),t)void 0===Qt[i]&&this._addAttr(i,t[i]);this.reset(),this.onFinish=t.onFinish,this.onReset=t.onReset}return u.prototype._addAttr=function(t,e){var i,n,r,o,a,s,h,l,c=this.node,d=c._id;if((r=u.tweens[d][t])&&delete u.attrs[d][r][t],i=c.getAttr(t),O._isArray(e))if(n=[],a=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(h=i,i=O._prepareArrayForTween(i,e,c.closed())):(s=e,e=O._prepareArrayForTween(e,i,c.closed()))),0===t.indexOf("fill"))for(o=0;othis.dataArray[i].pathLength;)t-=this.dataArray[i].pathLength,++i;if(i===n)return{x:(e=this.dataArray[i-1].points.slice(-2))[0],y:e[1]};if(t<.01)return{x:(e=this.dataArray[i].points.slice(0,2))[0],y:e[1]};var r=this.dataArray[i],o=r.points;switch(r.command){case"L":return u.getPointOnLine(t,r.start.x,r.start.y,o[0],o[1]);case"C":return u.getPointOnCubicBezier(t/r.pathLength,r.start.x,r.start.y,o[0],o[1],o[2],o[3],o[4],o[5]);case"Q":return u.getPointOnQuadraticBezier(t/r.pathLength,r.start.x,r.start.y,o[0],o[1],o[2],o[3]);case"A":var a=o[0],s=o[1],h=o[2],l=o[3],c=o[4],d=o[5],p=o[6];return c+=d*t/r.pathLength,u.getPointOnEllipticalArc(a,s,h,l,c,p)}return null},u.getLineLength=function(t,e,i,n){return Math.sqrt((i-t)*(i-t)+(n-e)*(n-e))},u.getPointOnLine=function(t,e,i,n,r,o,a){void 0===o&&(o=e),void 0===a&&(a=i);var s=(r-i)/(n-e+1e-8),h=Math.sqrt(t*t/(1+s*s));n>>1,k=_.slice(0,1+P),T=this._getTextWidth(k)+v;T<=l?(b=1+P,w=k+(g?"…":""),C=T):x=P}if(!w)break;if(f){var M,A=_[w.length];0<(M=(" "===A||"-"===A)&&C<=l?w.length:Math.max(w.lastIndexOf(" "),w.lastIndexOf("-"))+1)&&(b=M,w=w.slice(0,b),C=this._getTextWidth(w))}if(w=w.trimRight(),this._addTextLine(w),i=Math.max(i,C),d+=n,!u||s&&ce?g=ve.getPointOnLine(e,f.x,f.y,v.points[0],v.points[1],f.x,f.y):v=void 0;break;case"A":var a=v.points[4],s=v.points[5],h=v.points[4]+s;0===m?m=a+1e-8:iv.pathLength?1e-8:e/v.pathLength:i>W,0!==C?(C=255/C,k[s]=(l*B>>W)*C,k[s+1]=(c*B>>W)*C,k[s+2]=(d*B>>W)*C):k[s]=k[s+1]=k[s+2]=0,l-=u,c-=f,d-=g,p-=v,u-=F.r,f-=F.g,g-=F.b,v-=F.a,o=h+((o=i+e+1)>W,0>W)*C,k[o+1]=(c*B>>W)*C,k[o+2]=(d*B>>W)*C):k[o]=k[o+1]=k[o+2]=0,l-=u,c-=f,d-=g,p-=v,u-=F.r,f-=F.g,g-=F.b,v-=F.a,o=i+((o=n+L) { * @name Konva.Node#getStage * @returns {Konva.Stage} */ - getStage() { + getStage(): any { return this._getCache(STAGE, this._getStage); } - _getStage() { + + _getStage(): Stage | undefined { var parent = this.getParent(); if (parent) { return parent.getStage(); diff --git a/src/PointerEvents.ts b/src/PointerEvents.ts new file mode 100644 index 00000000..f1fa50d6 --- /dev/null +++ b/src/PointerEvents.ts @@ -0,0 +1,59 @@ +import { KonvaEventObject } from './types'; + +import { Shape } from './Shape'; +import { Stage } from './Stage'; + +const Captures = new Map(); + +export interface KonvaPointerEvent extends KonvaEventObject { + pointerId: number; +} + +let implicitRelease = null; + +export function getCapturedShape(pointerId: number) { + return Captures.get(pointerId); +} + +export function createEvent(evt: PointerEvent): KonvaPointerEvent { + return { + evt, + pointerId: evt.pointerId + } as any; +} + +export function hasPointerCapture(pointerId: number, shape: Shape | Stage) { + return Captures.get(pointerId) === shape; +} + +export function setPointerCapture(pointerId: number, shape: Shape | Stage) { + releaseCapture(pointerId); + + const { content } = shape.getStage(); + + content.setPointerCapture(pointerId); + + Captures.set(pointerId, shape); + + shape._fire( + 'gotpointercapture', + createEvent(new PointerEvent('gotpointercapture')) + ); +} + +export function releaseCapture(pointerId: number, target?: Shape | Stage) { + const shape = Captures.get(pointerId); + + if (!shape) return; + + const { content } = shape.getStage(); + + content.releasePointerCapture(pointerId); + + Captures.delete(pointerId); + + shape._fire( + 'lostpointercapture', + createEvent(new PointerEvent('lostpointercapture')) + ); +} diff --git a/src/Shape.ts b/src/Shape.ts index 792c3482..80316865 100644 --- a/src/Shape.ts +++ b/src/Shape.ts @@ -10,6 +10,7 @@ import { import { Context } from './Context'; import { _registerNode } from './Global'; +import * as PointerEvents from './PointerEvents'; import { GetSet, Vector2d } from './types'; @@ -395,8 +396,8 @@ export class Shape extends Node< // why do we need buffer canvas? // it give better result when a shape has // stroke with fill and with some opacity - _useBufferCanvas(caching) { - return ( + _useBufferCanvas(caching): boolean { + return !!( (!caching || this.hasShadow()) && this.perfectDrawEnabled() && this.getAbsoluteOpacity() !== 1 && @@ -711,6 +712,18 @@ export class Shape extends Node< return this; } + hasPointerCapture(pointerId: number): boolean { + return PointerEvents.hasPointerCapture(pointerId, this); + } + + setPointerCapture(pointerId: number) { + PointerEvents.setPointerCapture(pointerId, this); + } + + releaseCapture(pointerId: number) { + PointerEvents.releaseCapture(pointerId, this); + } + draggable: GetSet; embossBlend: GetSet; diff --git a/src/Stage.ts b/src/Stage.ts index 2bfdb4b2..40ade0b7 100644 --- a/src/Stage.ts +++ b/src/Stage.ts @@ -8,6 +8,7 @@ import { Shape } from './Shape'; import { BaseLayer } from './BaseLayer'; import { DD } from './DragAndDrop'; import { _registerNode } from './Global'; +import * as PointerEvents from './PointerEvents'; export interface StageConfig extends ContainerConfig { container: HTMLDivElement | string; @@ -24,6 +25,12 @@ var STAGE = 'Stage', MOUSEMOVE = 'mousemove', MOUSEDOWN = 'mousedown', MOUSEUP = 'mouseup', + // TODO: add them into "on" method docs and into site docs + POINTERMOVE = 'pointermove', + POINTERDOWN = 'pointerdown', + POINTERUP = 'pointerup', + POINTERCANCEL = 'pointercancel', + LOSTPOINTERCAPTURE = 'lostpointercapture', CONTEXTMENU = 'contextmenu', CLICK = 'click', DBL_CLICK = 'dblclick', @@ -46,6 +53,9 @@ var STAGE = 'Stage', CONTENT_DBL_TAP = 'contentDbltap', CONTENT_TAP = 'contentTap', CONTENT_TOUCHMOVE = 'contentTouchmove', + CONTENT_POINTERMOVE = 'contentPointermove', + CONTENT_POINTERDOWN = 'contentPointerdown', + CONTENT_POINTERUP = 'contentPointerup', CONTENT_WHEEL = 'contentWheel', RELATIVE = 'relative', KONVA_CONTENT = 'konvajs-content', @@ -65,7 +75,12 @@ var STAGE = 'Stage', TOUCHEND, MOUSEOVER, WHEEL, - CONTEXTMENU + CONTEXTMENU, + POINTERDOWN, + POINTERMOVE, + POINTERUP, + POINTERCANCEL, + LOSTPOINTERCAPTURE ], // cached variables eventsLength = EVENTS.length; @@ -709,6 +724,73 @@ export class Stage extends Container { } this._fire(CONTENT_WHEEL, { evt: evt }); } + + _pointerdown(evt: PointerEvent) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + + const shape = + PointerEvents.getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + + if (shape) { + shape._fireAndBubble(POINTERDOWN, PointerEvents.createEvent(evt)); + } + } + + _pointermove(evt: PointerEvent) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + + const shape = + PointerEvents.getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + + if (shape) { + shape._fireAndBubble(POINTERMOVE, PointerEvents.createEvent(evt)); + } + } + + _pointerup(evt: PointerEvent) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + const shape = + PointerEvents.getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + + if (shape) { + shape._fireAndBubble(POINTERUP, PointerEvents.createEvent(evt)); + } + + PointerEvents.releaseCapture(evt.pointerId); + } + + _pointercancel(evt: PointerEvent) { + if (!Konva._pointerEventsEnabled) { + return; + } + this.setPointersPositions(evt); + const shape = + PointerEvents.getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()); + + if (shape) { + shape._fireAndBubble(POINTERUP, PointerEvents.createEvent(evt)); + } + + PointerEvents.releaseCapture(evt.pointerId); + } + + _lostpointercapture(evt: PointerEvent) { + PointerEvents.releaseCapture(evt.pointerId); + } + /** * manually register pointers positions (mouse/touch) in the stage. * So you can use stage.getPointerPosition(). Usually you don't need to use that method diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts index 60dacf56..84f47c78 100644 --- a/src/shapes/Image.ts +++ b/src/shapes/Image.ts @@ -36,7 +36,7 @@ export interface ImageConfig extends ShapeConfig { */ export class Image extends Shape { _useBufferCanvas() { - return ( + return !!( (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke() && this.getStage() diff --git a/src/shapes/Sprite.ts b/src/shapes/Sprite.ts index 537d3038..cd877ad7 100644 --- a/src/shapes/Sprite.ts +++ b/src/shapes/Sprite.ts @@ -150,11 +150,13 @@ export class Sprite extends Shape { context.closePath(); context.fillShape(this); } + _useBufferCanvas() { return ( (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke() ); } + _setInterval() { var that = this; this.interval = setInterval(function() { diff --git a/test/functional/PointerEvents-test.js b/test/functional/PointerEvents-test.js new file mode 100644 index 00000000..15172ab5 --- /dev/null +++ b/test/functional/PointerEvents-test.js @@ -0,0 +1,235 @@ +/* eslint-disable max-nested-callbacks */ +suite('PointerEvents', function() { + Konva._pointerEventsEnabled = true; + // ====================================================== + test('pointerdown pointerup pointermove', function(done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4 + }); + + // mobile events + var pointerdown = false; + var pointerup = false; + var pointermove = false; + + /* + * mobile + */ + circle.on('pointerdown', function() { + pointerdown = true; + }); + + circle.on('pointerup', function() { + pointerup = true; + }); + + circle.on('pointermove', function() { + pointermove = true; + }); + + layer.add(circle); + stage.add(layer); + + var top = stage.content.getBoundingClientRect().top; + + // touchstart circle + stage._pointerdown({ + clientX: 289, + clientY: 100 + top, + pointerId: 1, + preventDefault: function() {} + }); + + assert(pointerdown, '1) pointerdown should be true'); + assert(!pointermove, '1) pointermove should be false'); + assert(!pointerup, '1) pointerup should be false'); + + // pointerup circle + stage._pointerup({ + touches: [], + preventDefault: function() {} + }); + + assert(pointerdown, '2) pointerdown should be true'); + assert(!pointermove, '2) pointermove should be false'); + assert(pointerup, '2) pointerup should be true'); + + // pointerdown circle + stage._pointerdown({ + touches: [ + { + clientX: 289, + clientY: 100 + top + } + ], + preventDefault: function() {} + }); + + assert(pointerdown, '3) pointerdown should be true'); + assert(!pointermove, '3) pointermove should be false'); + assert(pointerup, '3) pointerup should be true'); + + // pointerup circle to triger dbltap + stage._pointerup({ + touches: [], + preventDefault: function() {} + }); + // end drag is tied to document mouseup and pointerup event + // which can't be simulated. call _endDrag manually + //Konva.DD._endDrag(); + + assert(pointerdown, '4) pointerdown should be true'); + assert(!pointermove, '4) pointermove should be false'); + assert(pointerup, '4) pointerup should be true'); + + setTimeout(function() { + // pointermove circle + stage._pointermove({ + touches: [ + { + clientX: 290, + clientY: 100 + top + } + ], + preventDefault: function() {} + }); + + assert(pointerdown, '5) pointerdown should be true'); + assert(pointermove, '5) pointermove should be true'); + assert(pointerup, '5) pointerup should be true'); + + done(); + }, 17); + }); + + // ====================================================== + test('pointer capture', function(done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4 + }); + + var circle2 = new Konva.Circle({ + x: stage.getWidth() / 2, + y: 20, + radius: 20, + fill: 'red', + stroke: 'black', + strokeWidth: 4 + }); + + // mobile events + var downCount = 0; + var otherDownCount = 0; + + var pointerup = false; + var pointermove = false; + + circle2.on('pointerdown', function() { + otherDownCount++; + }); + + circle.on('pointerdown', function(event) { + downCount++; + this.setPointerCapture(event.pointerId); + }); + + circle.on('pointerup', function(evt) { + assert(this.hasPointerCapture(evt.pointerId), 'circle released capture'); + pointerup = true; + }); + + circle.on('pointermove', function(evt) { + assert(this.hasPointerCapture(evt.pointerId), 'circle has capture'); + pointermove = true; + }); + + layer.add(circle); + layer.add(circle2); + stage.add(layer); + + var top = stage.content.getBoundingClientRect().top; + + // on circle 2 to confirm it works + stage._pointerdown({ + clientX: 289, + clientY: 10 + top, + pointerId: 0, + preventDefault: function() {} + }); + + assert(otherDownCount === 1, '6) otherDownCount should be 1'); + assert(downCount === 0, '6) downCount should be 0'); + assert(!pointermove, '6) pointermove should be false'); + assert(!pointerup, '6) pointerup should be false'); + + // on circle with capture + stage._pointerdown({ + clientX: 289, + clientY: 100 + top, + pointerId: 1, + preventDefault: function() {} + }); + + assert(otherDownCount === 1, '7) otherDownCount should be 1'); + assert(downCount === 1, '7) downCount should be 1'); + assert(!pointermove, '7) pointermove should be false'); + assert(!pointerup, '7) pointerup should be true'); + + // second pointerdown + stage._pointerdown({ + clientX: 289, + clientY: 10 + top, + pointerId: 1, + preventDefault: function() {} + }); + + assert(otherDownCount === 1, '8) otherDownCount should be 1'); + assert(downCount === 2, '8) pointerdown should be 2'); + assert(!pointermove, '8) pointermove should be false'); + assert(!pointerup, '8) pointerup should be true'); + + setTimeout(function() { + // pointermove over circle 2 + stage._pointermove({ + clientX: 290, + clientY: 10 + top, + pointerId: 1, + preventDefault: function() {} + }); + + assert(otherDownCount === 1, '9) otherDownCount should be 1'); + assert(pointermove, '9) pointermove should be true'); + + stage._pointerup({ + pointerId: 1, + preventDefault: function() {} + }); + + stage._pointerdown({ + clientX: 289, + clientY: 10 + top, + pointerId: 1, + preventDefault: function() {} + }); + + assert(otherDownCount === 2, '10) otherDownCount should be 1'); + assert(pointerup, '10) pointerup should be true'); + + done(); + }, 17); + }); +}); diff --git a/test/runner.html b/test/runner.html index 4c4c2101..751c696f 100644 --- a/test/runner.html +++ b/test/runner.html @@ -177,6 +177,7 @@ + @@ -188,4 +189,4 @@ - \ No newline at end of file + diff --git a/test/runner.js b/test/runner.js index c048ade3..7f35f9bd 100644 --- a/test/runner.js +++ b/test/runner.js @@ -329,4 +329,44 @@ Konva.Stage.prototype.simulateTouchEnd = function(pos) { Konva.DD._endDragAfter(evt); }; +Konva.Stage.prototype.simulatePointerDown = function(pos) { + var top = this.content.getBoundingClientRect().top; + + this._mousedown({ + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1 + }); +}; + +Konva.Stage.prototype.simulatePointerMove = function(pos) { + var top = this.content.getBoundingClientRect().top; + + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1 + }; + + this._mousemove(evt); + Konva.DD._drag(evt); +}; + +Konva.Stage.prototype.simulatePointerUp = function(pos) { + var top = this.content.getBoundingClientRect().top; + + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1 + }; + + Konva.DD._endDragBefore(evt); + this._mouseup(evt); + Konva.DD._endDragAfter(evt); +}; + init();