diff --git a/dist/jquery.sketchable.full.min.js b/dist/jquery.sketchable.full.min.js index e0e077d..021e49c 100644 --- a/dist/jquery.sketchable.full.min.js +++ b/dist/jquery.sketchable.full.min.js @@ -1,2 +1,2 @@ -/*! jSketch drawing lib (all in one) | v2.0.0 | 2017-11-17 */ -!function(a){var b=function(a,b){return new c(a,b)},c=function(a,b){return a?(this.context(a),this.stageWidth=a.width,this.stageHeight=a.height,this.data={},this.drawingDefaults(b),this):void 0};b.fn=c.prototype={context:function(a){if(null===a)throw"No canvas element specified.";return this.canvas=a,this.graphics=a.getContext("2d"),this},drawingDefaults:function(a){return"undefined"==typeof a&&(a={}),"undefined"==typeof a.fillStyle&&(a.fillStyle="#F00"),"undefined"==typeof a.strokeStyle&&(a.strokeStyle="#F0F"),"undefined"==typeof a.lineWidth&&(a.lineWidth=2),"undefined"==typeof a.lineCap&&(a.lineCap="round"),"undefined"==typeof a.lineJoin&&(a.lineJoin="round"),"undefined"==typeof a.miterLimit&&(a.miterLimit=10),this.saveGraphics(a),this.restoreGraphics(),this},size:function(a,b){return this.stageWidth=a,this.stageHeight=b,this.canvas.width=a,this.canvas.height=b,this.restoreGraphics(),this},background:function(a){return this.beginFill(a),this.graphics.fillRect(0,0,this.stageWidth,this.stageHeight),this.endFill(),this},stage:function(a,b,c){return this.size(a,b).background(c),this},beginFill:function(a){return this.saveGraphics(),this.graphics.fillStyle=a,this},endFill:function(){return this.restoreGraphics(),this},lineStyle:function(a,b,c,d,e){var f={strokeStyle:a||this.graphics.strokeStyle,lineWidth:b||this.graphics.lineWidth,lineCap:c||this.graphics.lineCap,lineJoin:d||this.graphics.lineJoin,miterLimit:e||this.graphics.miterLimit};return this.saveGraphics(f),this.restoreGraphics(),this},moveTo:function(a,b){return this.graphics.moveTo(a,b),this},lineTo:function(a,b){return this.graphics.lineTo(a,b),this},line:function(a,b,c,d){return this.graphics.moveTo(a,b),this.lineTo(c,d),this},curveTo:function(a,b,c,d){return this.graphics.quadraticCurveTo(c,d,a,b),this},curve:function(a,b,c,d,e,f){return this.graphics.moveTo(a,b),this.curveTo(c,d,e,f),this},stroke:function(){return this.graphics.stroke(),this},strokeRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.strokeRect(a,b,c,d),this.graphics.closePath(),this},fillRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.fillRect(a,b,c,d),this.graphics.closePath(),this},strokeCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.stroke(),this.graphics.closePath(),this},fillCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.fill(),this.graphics.closePath(),this},radialCircle:function(a,b,c,d,e){var f=this.graphics.createRadialGradient(a,b,c,a,b,d||5);e&&"array"===e.constructor.name||(e=[this.graphics.fillStyle,"white"]);for(var g=0;g1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function e(a){return a.originalEvent.touches?!1:void l(a)}function f(a){return a.originalEvent.touches?!1:void m(a)}function g(a){return a.originalEvent.touches?!1:void n(a)}function h(b,c){var d=a(b.target),e=d.data(o),f=e.options;if(f.multitouch)for(var g=b.originalEvent.changedTouches,h=0;h0&&g.sketch.beginFill(h.graphics.fillStyle).fillCircle(i.x,i.y,h.graphics.firstPointSize).endFill(),g.coords[e]||(g.coords[e]=[]),g.coords[e].length>0&&(g.strokes.push(g.coords[e]),g.coords[e]=[]),d(e,g,i),"function"==typeof h.events.mousedown&&h.events.mousedown(f,g,b)}}function m(b){var e=b.identifier||0,f=a(b.target),g=f.data(o),h=g.options;if(h.interactive&&(h.mouseupMovements&&0!==g.strokes.length||g.sketch.isDrawing)){var i=c(b);if(g.sketch.isDrawing){var j=g.coords[e][g.coords[e].length-1];g.sketch.beginPath().line(j[0],j[1],i.x,i.y).stroke().closePath()}d(e,g,i),"function"==typeof h.events.mousemove&&h.events.mousemove(f,g,b)}}function n(b){var c=b.identifier||0,d=a(b.target),e=d.data(o),f=e.options;f.interactive&&(e.sketch.isDrawing=!1,e.strokes.push(e.coords[c]),e.coords[c]=[],"function"==typeof f.events.mouseup&&f.events.mouseup(d,e,b))}var o="sketchable",p={init:function(c){var d=a.extend(!0,{},a.fn.sketchable.defaults,c||{});return this.each(function(){var c=a(this),h=c.data(o);h||(c.bind("mousedown",e),c.bind("mousemove",f),c.bind("mouseup",g),c.bind("touchstart",i),c.bind("touchmove",j),c.bind("touchend",k),b(c,d));var l=new jSketch(this,d.graphics);c.data(o,{strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:l,options:d}),d.events&&"function"==typeof d.events.init&&d.events.init(c,c.data(o));for(var m in a.fn.sketchable.plugins)a.fn.sketchable.plugins[m](c)})},config:function(c){return c?this.each(function(){var d=a(this),e=d.data(o);e.options=a.extend(!0,{},a.fn.sketchable.defaults,e.options,c),b(d)}):a(this).data(o)},strokes:function(b){if(b)return this.each(function(){var c=a(this),d=c.data(o);d.strokes=b});var c=a(this).data(o);return c.strokes},handler:function(b){return this.each(function(){var c=a(this),d=c.data(o);b(c,d)})},clear:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;c.sketch&&(c.sketch.clear(),c.strokes=[],c.coords={}),d&&"function"==typeof d.events.clear&&d.events.clear(b,c)})},reset:function(b){return this.each(function(){var c=a(this),d=c.data(o)||{},e=d.options;c.sketchable("destroy").sketchable(b),e&&"function"==typeof e.events.reset&&e.events.reset(c,d)})},destroy:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;b.unbind("mouseup",g),b.unbind("mousemove",f),b.unbind("mousedown",e),b.unbind("touchstart",i),b.unbind("touchmove",j),b.unbind("touchend",k),b.removeData(o),d&&"function"==typeof d.events.destroy&&d.events.destroy(b,c)})}};a.fn.sketchable=function(b){return"object"!=typeof b&&b?p[b]?p[b].apply(this,Array.prototype.slice.call(arguments,1)):(a.error("Unknown method: "+b),this):p.init.apply(this,arguments)},a.fn.sketchable.api=p,a.fn.sketchable.plugins={},a.fn.sketchable.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,filterCoords:!1,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}}}(jQuery),function(a){function b(b){function c(){if(h>0){h--;var a=new Image;a.src=g[h].image,a.onload=function(){e(this)}}}function d(){if(h0?g[h].strokes=c.strokes.slice():(g.push({image:b[0].toDataURL(),strokes:c.strokes.slice()}),h++)}),this},this.init=function(){return a(document).off("keypress",f),a(document).on("keypress",f),this},this.destroy=function(){return a(document).off("keypress",f),this.reset()}}var c="sketchable";a.fn.sketchable.plugins.memento=function(d){function e(a){if(!f.options[a+"$bound"])if(f.options[a+"$bound"]=!0,f.options.events&&"function"==typeof f.options.events[a]){var b=f.options.events[a];f.options.events[a]=function(){b.apply(d,arguments),g[a].apply(d,arguments)}}else f.options.events[a]=g[a]}for(var f=d.sketchable("config"),g={clear:function(a,b){b.memento.reset().save()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},h="mouseup clear destroy".split(" "),i=0;i1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function f(a){return a.originalEvent.touches?!1:void l(a)}function g(a){return a.originalEvent.touches?!1:void m(a)}function h(a){return a.originalEvent.touches?!1:void n(a)}function i(a){o(a,l),a.preventDefault()}function j(a){o(a,m),a.preventDefault()}function k(a){o(a,n),a.preventDefault()}function l(b){if(3===b.which)return!1;var c=b.identifier||0,f=a(b.target),g=f.data(p),h=g.options;if(h.interactive){g.sketch.isDrawing=!0;var i=d(b);h.graphics.firstPointSize>0&&g.sketch.beginFill(h.graphics.fillStyle).fillCircle(i.x,i.y,h.graphics.firstPointSize).endFill();var j=g.coords[c];j||(j=[]),j.length>0&&g.strokes.push(j),g.coords[c]=[],e(c,g,i),"function"==typeof h.events.mousedown&&h.events.mousedown(f,g,b)}}function m(b){var c=b.identifier||0,f=a(b.target),g=f.data(p),h=g.options;if(h.interactive&&(h.mouseupMovements&&0!==g.strokes.length||g.sketch.isDrawing)){var i=d(b),j=g.coords[c],k=j[j.length-1];if(k){var l=g.sketch.beginPath();g.sketch.isDrawing?l.lineStyle(h.graphics.strokeStyle,h.graphics.lineWidth):h.mouseupMovements.visible!==!1&&l.lineStyle(h.mouseupMovements.strokeStyle||"#DDD",h.mouseupMovements.lineWidth||1),l.line(k[0],k[1],i.x,i.y).stroke().closePath()}e(c,g,i),"function"==typeof h.events.mousemove&&h.events.mousemove(f,g,b)}}function n(b){var c=b.identifier||0,d=a(b.target),e=d.data(p),f=e.options;f.interactive&&(e.sketch.isDrawing=!1,e.strokes.push(e.coords[c]),e.coords[c]=[],"function"==typeof f.events.mouseup&&f.events.mouseup(d,e,b))}function o(b,c){var d=a(b.target),e=d.data(p),f=e.options;if(f.multitouch)for(var g=b.originalEvent.changedTouches,h=0;h-1){var e=c(q,b);return e.apply(this,d)}return q[b]?q[b].apply(this,d):(a.error("Unknown method: "+b),this)},a.fn.sketchable.api=q,a.fn.sketchable.plugins={},a.fn.sketchable.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,filterCoords:!1,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}}}(jQuery),function(a){function b(b){function c(a,c){b.sketchable("handler",function(b,d){d.sketch.clear(),d.sketch.graphics.drawImage(a,0,0),d.strokes=c.slice()})}function d(a){if(a.ctrlKey)switch(a.which){case 26:a.shiftKey?g.redo():g.undo();break;case 25:g.redo()}}var e=[],f=-1,g=this;this.undo=function(){return f>0&&(f--,this.restore()),this},this.redo=function(){return f0?e[f].strokes=c.strokes.slice():(e.push({image:b[0].toDataURL(),strokes:c.strokes.slice()}),f++)}),this},this.state=function(){return JSON.parse(JSON.stringify(e[f]))},this.restore=function(a){a||(a=e[f]);var b=new Image;b.src=a.image,b.onload=function(){c(this,a.strokes)}},this.init=function(){return a(document).off("keypress",d),a(document).on("keypress",d),this.save()},this.destroy=function(){return a(document).off("keypress",d),this.reset()}}var c="sketchable";a.fn.sketchable.plugins.memento=function(d){function e(a){if(!f.options["_bound$"+a])if(f.options["_bound$"+a]=!0,f.options.events&&"function"==typeof f.options.events[a]){var b=f.options.events[a];f.options.events[a]=function(){b.apply(d,arguments),g[a].apply(d,arguments)}}else f.options.events[a]=g[a]}for(var f=d.sketchable("config"),g={clear:function(a,b){b.memento.reset()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},h="mouseup clear destroy".split(" "),i=0;i0){h--;var a=new Image;a.src=g[h].image,a.onload=function(){e(this)}}}function d(){if(h0?g[h].strokes=c.strokes.slice():(g.push({image:b[0].toDataURL(),strokes:c.strokes.slice()}),h++)}),this},this.init=function(){return a(document).off("keypress",f),a(document).on("keypress",f),this},this.destroy=function(){return a(document).off("keypress",f),this.reset()}}var c="sketchable";a.fn.sketchable.plugins.memento=function(d){function e(a){if(!f.options[a+"$bound"])if(f.options[a+"$bound"]=!0,f.options.events&&"function"==typeof f.options.events[a]){var b=f.options.events[a];f.options.events[a]=function(){b.apply(d,arguments),g[a].apply(d,arguments)}}else f.options.events[a]=g[a]}for(var f=d.sketchable("config"),g={clear:function(a,b){b.memento.reset().save()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},h="mouseup clear destroy".split(" "),i=0;i0&&(f--,this.restore()),this},this.redo=function(){return f0?e[f].strokes=c.strokes.slice():(e.push({image:b[0].toDataURL(),strokes:c.strokes.slice()}),f++)}),this},this.state=function(){return JSON.parse(JSON.stringify(e[f]))},this.restore=function(a){a||(a=e[f]);var b=new Image;b.src=a.image,b.onload=function(){c(this,a.strokes)}},this.init=function(){return a(document).off("keypress",d),a(document).on("keypress",d),this.save()},this.destroy=function(){return a(document).off("keypress",d),this.reset()}}var c="sketchable";a.fn.sketchable.plugins.memento=function(d){function e(a){if(!f.options["_bound$"+a])if(f.options["_bound$"+a]=!0,f.options.events&&"function"==typeof f.options.events[a]){var b=f.options.events[a];f.options.events[a]=function(){b.apply(d,arguments),g[a].apply(d,arguments)}}else f.options.events[a]=g[a]}for(var f=d.sketchable("config"),g={clear:function(a,b){b.memento.reset()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},h="mouseup clear destroy".split(" "),i=0;i1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function e(a){return a.originalEvent.touches?!1:void l(a)}function f(a){return a.originalEvent.touches?!1:void m(a)}function g(a){return a.originalEvent.touches?!1:void n(a)}function h(b,c){var d=a(b.target),e=d.data(o),f=e.options;if(f.multitouch)for(var g=b.originalEvent.changedTouches,h=0;h0&&g.sketch.beginFill(h.graphics.fillStyle).fillCircle(i.x,i.y,h.graphics.firstPointSize).endFill(),g.coords[e]||(g.coords[e]=[]),g.coords[e].length>0&&(g.strokes.push(g.coords[e]),g.coords[e]=[]),d(e,g,i),"function"==typeof h.events.mousedown&&h.events.mousedown(f,g,b)}}function m(b){var e=b.identifier||0,f=a(b.target),g=f.data(o),h=g.options;if(h.interactive&&(h.mouseupMovements&&0!==g.strokes.length||g.sketch.isDrawing)){var i=c(b);if(g.sketch.isDrawing){var j=g.coords[e][g.coords[e].length-1];g.sketch.beginPath().line(j[0],j[1],i.x,i.y).stroke().closePath()}d(e,g,i),"function"==typeof h.events.mousemove&&h.events.mousemove(f,g,b)}}function n(b){var c=b.identifier||0,d=a(b.target),e=d.data(o),f=e.options;f.interactive&&(e.sketch.isDrawing=!1,e.strokes.push(e.coords[c]),e.coords[c]=[],"function"==typeof f.events.mouseup&&f.events.mouseup(d,e,b))}var o="sketchable",p={init:function(c){var d=a.extend(!0,{},a.fn.sketchable.defaults,c||{});return this.each(function(){var c=a(this),h=c.data(o);h||(c.bind("mousedown",e),c.bind("mousemove",f),c.bind("mouseup",g),c.bind("touchstart",i),c.bind("touchmove",j),c.bind("touchend",k),b(c,d));var l=new jSketch(this,d.graphics);c.data(o,{strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:l,options:d}),d.events&&"function"==typeof d.events.init&&d.events.init(c,c.data(o));for(var m in a.fn.sketchable.plugins)a.fn.sketchable.plugins[m](c)})},config:function(c){return c?this.each(function(){var d=a(this),e=d.data(o);e.options=a.extend(!0,{},a.fn.sketchable.defaults,e.options,c),b(d)}):a(this).data(o)},strokes:function(b){if(b)return this.each(function(){var c=a(this),d=c.data(o);d.strokes=b});var c=a(this).data(o);return c.strokes},handler:function(b){return this.each(function(){var c=a(this),d=c.data(o);b(c,d)})},clear:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;c.sketch&&(c.sketch.clear(),c.strokes=[],c.coords={}),d&&"function"==typeof d.events.clear&&d.events.clear(b,c)})},reset:function(b){return this.each(function(){var c=a(this),d=c.data(o)||{},e=d.options;c.sketchable("destroy").sketchable(b),e&&"function"==typeof e.events.reset&&e.events.reset(c,d)})},destroy:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;b.unbind("mouseup",g),b.unbind("mousemove",f),b.unbind("mousedown",e),b.unbind("touchstart",i),b.unbind("touchmove",j),b.unbind("touchend",k),b.removeData(o),d&&"function"==typeof d.events.destroy&&d.events.destroy(b,c)})}};a.fn.sketchable=function(b){return"object"!=typeof b&&b?p[b]?p[b].apply(this,Array.prototype.slice.call(arguments,1)):(a.error("Unknown method: "+b),this):p.init.apply(this,arguments)},a.fn.sketchable.api=p,a.fn.sketchable.plugins={},a.fn.sketchable.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,filterCoords:!1,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}}}(jQuery); \ No newline at end of file +!function(a){function b(a,b){b||(b=a.data(p).options),b.cssCursors&&(a[0].style.cursor=b.interactive?"pointer":"not-allowed"),this.onselectstart=function(){return!1}}function c(a,b){b=b.split(".");for(var c=0;c1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function f(a){return a.originalEvent.touches?!1:void l(a)}function g(a){return a.originalEvent.touches?!1:void m(a)}function h(a){return a.originalEvent.touches?!1:void n(a)}function i(a){o(a,l),a.preventDefault()}function j(a){o(a,m),a.preventDefault()}function k(a){o(a,n),a.preventDefault()}function l(b){if(3===b.which)return!1;var c=b.identifier||0,f=a(b.target),g=f.data(p),h=g.options;if(h.interactive){g.sketch.isDrawing=!0;var i=d(b);h.graphics.firstPointSize>0&&g.sketch.beginFill(h.graphics.fillStyle).fillCircle(i.x,i.y,h.graphics.firstPointSize).endFill();var j=g.coords[c];j||(j=[]),j.length>0&&g.strokes.push(j),g.coords[c]=[],e(c,g,i),"function"==typeof h.events.mousedown&&h.events.mousedown(f,g,b)}}function m(b){var c=b.identifier||0,f=a(b.target),g=f.data(p),h=g.options;if(h.interactive&&(h.mouseupMovements&&0!==g.strokes.length||g.sketch.isDrawing)){var i=d(b),j=g.coords[c],k=j[j.length-1];if(k){var l=g.sketch.beginPath();g.sketch.isDrawing?l.lineStyle(h.graphics.strokeStyle,h.graphics.lineWidth):h.mouseupMovements.visible!==!1&&l.lineStyle(h.mouseupMovements.strokeStyle||"#DDD",h.mouseupMovements.lineWidth||1),l.line(k[0],k[1],i.x,i.y).stroke().closePath()}e(c,g,i),"function"==typeof h.events.mousemove&&h.events.mousemove(f,g,b)}}function n(b){var c=b.identifier||0,d=a(b.target),e=d.data(p),f=e.options;f.interactive&&(e.sketch.isDrawing=!1,e.strokes.push(e.coords[c]),e.coords[c]=[],"function"==typeof f.events.mouseup&&f.events.mouseup(d,e,b))}function o(b,c){var d=a(b.target),e=d.data(p),f=e.options;if(f.multitouch)for(var g=b.originalEvent.changedTouches,h=0;h-1){var e=c(q,b);return e.apply(this,d)}return q[b]?q[b].apply(this,d):(a.error("Unknown method: "+b),this)},a.fn.sketchable.api=q,a.fn.sketchable.plugins={},a.fn.sketchable.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,filterCoords:!1,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}}}(jQuery); \ No newline at end of file diff --git a/dist/sketchable.full.min.js b/dist/sketchable.full.min.js index ca004e8..1e95365 100644 --- a/dist/sketchable.full.min.js +++ b/dist/sketchable.full.min.js @@ -1,2 +1,2 @@ -/*! jSketch drawing lib (all in one) | v2.0.0 | 2017-11-17 */ -!function(a){var b=function(a,b){return new c(a,b)},c=function(a,b){return a?(this.context(a),this.stageWidth=a.width,this.stageHeight=a.height,this.data={},this.drawingDefaults(b),this):void 0};b.fn=c.prototype={context:function(a){if(null===a)throw"No canvas element specified.";return this.canvas=a,this.graphics=a.getContext("2d"),this},drawingDefaults:function(a){return"undefined"==typeof a&&(a={}),"undefined"==typeof a.fillStyle&&(a.fillStyle="#F00"),"undefined"==typeof a.strokeStyle&&(a.strokeStyle="#F0F"),"undefined"==typeof a.lineWidth&&(a.lineWidth=2),"undefined"==typeof a.lineCap&&(a.lineCap="round"),"undefined"==typeof a.lineJoin&&(a.lineJoin="round"),"undefined"==typeof a.miterLimit&&(a.miterLimit=10),this.saveGraphics(a),this.restoreGraphics(),this},size:function(a,b){return this.stageWidth=a,this.stageHeight=b,this.canvas.width=a,this.canvas.height=b,this.restoreGraphics(),this},background:function(a){return this.beginFill(a),this.graphics.fillRect(0,0,this.stageWidth,this.stageHeight),this.endFill(),this},stage:function(a,b,c){return this.size(a,b).background(c),this},beginFill:function(a){return this.saveGraphics(),this.graphics.fillStyle=a,this},endFill:function(){return this.restoreGraphics(),this},lineStyle:function(a,b,c,d,e){var f={strokeStyle:a||this.graphics.strokeStyle,lineWidth:b||this.graphics.lineWidth,lineCap:c||this.graphics.lineCap,lineJoin:d||this.graphics.lineJoin,miterLimit:e||this.graphics.miterLimit};return this.saveGraphics(f),this.restoreGraphics(),this},moveTo:function(a,b){return this.graphics.moveTo(a,b),this},lineTo:function(a,b){return this.graphics.lineTo(a,b),this},line:function(a,b,c,d){return this.graphics.moveTo(a,b),this.lineTo(c,d),this},curveTo:function(a,b,c,d){return this.graphics.quadraticCurveTo(c,d,a,b),this},curve:function(a,b,c,d,e,f){return this.graphics.moveTo(a,b),this.curveTo(c,d,e,f),this},stroke:function(){return this.graphics.stroke(),this},strokeRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.strokeRect(a,b,c,d),this.graphics.closePath(),this},fillRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.fillRect(a,b,c,d),this.graphics.closePath(),this},strokeCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.stroke(),this.graphics.closePath(),this},fillCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.fill(),this.graphics.closePath(),this},radialCircle:function(a,b,c,d,e){var f=this.graphics.createRadialGradient(a,b,c,a,b,d||5);e&&"array"===e.constructor.name||(e=[this.graphics.fillStyle,"white"]);for(var g=0;g1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function g(a){return a.touches?!1:void n(a)}function h(a){return a.touches?!1:void o(a)}function i(a){return a.touches?!1:void p(a)}function j(a,b){var c=a.target,d=dataBind(c)[q],e=d.options;if(e.multitouch)for(var f=a.changedTouches,g=0;g0&&d.sketch.beginFill(g.graphics.fillStyle).fillCircle(h.x,h.y,g.graphics.firstPointSize).endFill(),d.coords[b]&&d.coords[b].length>0&&(d.strokes.push(d.coords[b].slice()),d.coords[b]=[]),f(b,d,h),"function"==typeof g.events.mousedown&&g.events.mousedown(c,d,a)}}function o(a){var b=a.identifier||0,c=a.target,d=dataBind(c)[q],g=d.options;if(g.interactive&&(g.mouseupMovements&&0!==d.strokes.length||d.sketch.isDrawing)){var h=e(a);if(d.sketch.isDrawing){var i=d.coords[b][d.coords[b].length-1];d.sketch.beginPath().line(i[0],i[1],h.x,h.y).stroke().closePath()}f(b,d,h),"function"==typeof g.events.mousemove&&g.events.mousemove(c,d,a)}}function p(a){var b=a.identifier||0,c=a.target,d=dataBind(c)[q],e=d.options;e.interactive&&(d.sketch.isDrawing=!1,d.strokes.push(d.coords[b].slice()),d.coords[b]=[],"function"==typeof e.events.mouseup&&e.events.mouseup(c,d,a))}var q="sketchable",r=a.document;b.prototype={init:function(a){var a=deepExtend({},b.prototype.defaults,a||{}),c=this.elem,e=dataBind(c)[q];e||(Event.add(c,"mousedown",g),Event.add(c,"mousemove",h),Event.add(c,"mouseup",i),Event.add(c,"touchstart",k),Event.add(c,"touchmove",l),Event.add(c,"touchend",m),d(c,a));var f=new jSketch(c,a.graphics);dataBind(c)[q]=e={strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:f,options:a},"function"==typeof a.events.init&&a.events.init(c,e);for(var j in this.plugins)this.plugins[j](this);return this},config:function(a){var c=this.elem,e=dataBind(c)[q];return a?(e.options=deepExtend({},b.prototype.defaults,a||{}),d(c),this):e},strokes:function(a){var b=this.elem;if(a){var c=dataBind(b)[q];return c.strokes=a,this}var c=dataBind(b)[q];return c.strokes},handler:function(a){var b=this.elem,c=dataBind(b)[q];return a(b,c),this},clear:function(){var a=this.elem,b=dataBind(a)[q],c=b.options;return b.sketch.clear(),b.strokes=[],b.coords={},"function"==typeof c.events.clear&&c.events.clear(a,b),this},reset:function(a){var b=this.elem,c=dataBind(b)[q],a=c.options;return this.destroy().init(a),"function"==typeof a.events.reset&&a.events.reset(b,c),this},destroy:function(){var a=this.elem,b=dataBind(a)[q],c=b.options;return Event.remove(a,"mouseup",i),Event.remove(a,"mousemove",h),Event.remove(a,"mousedown",g),Event.remove(a,"touchstart",k),Event.remove(a,"touchmove",l),Event.remove(a,"touchend",m),dataBind(a)[q]=null,"function"==typeof c.events.destroy&&c.events.destroy(a,b),this}},b.prototype.plugins={},b.prototype.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,filterCoords:!1,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}},a.Sketchable=b}(this),function(a){function b(a){function b(){if(g>0){g--;var a=new Image;a.src=f[g].image,a.onload=function(){d(this)}}}function c(){if(g0?f[g].strokes=c.strokes.slice():(f.push({image:a.toDataURL(),strokes:c.strokes.slice()}),g++)}),this},this.init=function(){return Event.remove(document,"keypress",e),Event.add(document,"keypress",e),this},this.destroy=function(){return Event.remove(document,"keypress",e),this.reset()}}var c="sketchable";Sketchable.prototype.plugins.memento=function(a){function d(b){if(!e.options[b+"$bound"])if(e.options[b+"$bound"]=!0,e.options.events&&"function"==typeof e.options.events[b]){var c=e.options.events[b];e.options.events[b]=function(){c.apply(a,arguments),f[b].apply(a,arguments)}}else e.options.events[b]=f[b]}for(var e=a.config(),f={clear:function(a,b){b.memento.reset().save()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},g="mouseup clear destroy".split(" "),h=0;h1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function g(a){return a.touches?!1:void m(a)}function h(a){return a.touches?!1:void n(a)}function i(a){return a.touches?!1:void o(a)}function j(a){p(a,m),a.preventDefault()}function k(a){p(a,n),a.preventDefault()}function l(a){p(a,o),a.preventDefault()}function m(a){if(Event.isRightClick(a))return!1;var b=a.identifier||0,c=a.target,d=dataBind(c)[q],g=d.options;if(g.interactive){d.sketch.isDrawing=!0;var h=e(a);g.graphics.firstPointSize>0&&d.sketch.beginFill(g.graphics.fillStyle).fillCircle(h.x,h.y,g.graphics.firstPointSize).endFill();var i=d.coords[b];i||(i=[]),i.length>0&&d.strokes.push(i),d.coords[b]=[],f(b,d,h),"function"==typeof g.events.mousedown&&g.events.mousedown(c,d,a)}}function n(a){var b=a.identifier||0;if(elem=a.target,data=dataBind(elem)[q],options=data.options,options.interactive&&(options.mouseupMovements&&0!==data.strokes.length||data.sketch.isDrawing)){var c=e(a),d=data.coords[b],g=d[d.length-1];if(g){var h=data.sketch.beginPath();data.sketch.isDrawing?h.lineStyle(options.graphics.strokeStyle,options.graphics.lineWidth):options.mouseupMovements.visible!==!1&&h.lineStyle(options.mouseupMovements.strokeStyle||"#DDD",options.mouseupMovements.lineWidth||1),h.line(g[0],g[1],c.x,c.y).stroke().closePath()}f(b,data,c),"function"==typeof options.events.mousemove&&options.events.mousemove(elem,data,a)}}function o(a){var b=a.identifier||0;elem=a.target,data=dataBind(elem)[q],options=data.options,options.interactive&&(data.sketch.isDrawing=!1,data.strokes.push(data.coords[b]),data.coords[b]=[],"function"==typeof options.events.mouseup&&options.events.mouseup(elem,data,a))}function p(a,b){var c=a.target,d=dataBind(c)[q],e=d.options;if(e.multitouch)for(var f=a.changedTouches,g=0;g0&&(e--,this.restore()),this},this.redo=function(){return e0?d[e].strokes=c.strokes.slice():(d.push({image:a.toDataURL(),strokes:c.strokes.slice()}),e++)}),this},this.state=function(){return JSON.parse(JSON.stringify(d[e]))},this.restore=function(a){a||(a=d[e]);var c=new Image;c.src=a.image,c.onload=function(){b(this,a.strokes)}},this.init=function(){return Event.remove(document,"keypress",c),Event.add(document,"keypress",c),this.save()},this.destroy=function(){return Event.remove(document,"keypress",c),this.reset()}}var c="sketchable";Sketchable.prototype.plugins.memento=function(a){function d(b){if(!e.options["_bound$"+b])if(e.options["_bound$"+b]=!0,e.options.events&&"function"==typeof e.options.events[b]){var c=e.options.events[b];e.options.events[b]=function(){c.apply(a,arguments),f[b].apply(a,arguments)}}else e.options.events[b]=f[b]}for(var e=a.config(),f={clear:function(a,b){b.memento.reset()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},g="mouseup clear destroy".split(" "),h=0;h0){g--;var a=new Image;a.src=f[g].image,a.onload=function(){d(this)}}}function c(){if(g0?f[g].strokes=c.strokes.slice():(f.push({image:a.toDataURL(),strokes:c.strokes.slice()}),g++)}),this},this.init=function(){return Event.remove(document,"keypress",e),Event.add(document,"keypress",e),this},this.destroy=function(){return Event.remove(document,"keypress",e),this.reset()}}var c="sketchable";Sketchable.prototype.plugins.memento=function(a){function d(b){if(!e.options[b+"$bound"])if(e.options[b+"$bound"]=!0,e.options.events&&"function"==typeof e.options.events[b]){var c=e.options.events[b];e.options.events[b]=function(){c.apply(a,arguments),f[b].apply(a,arguments)}}else e.options.events[b]=f[b]}for(var e=a.config(),f={clear:function(a,b){b.memento.reset().save()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},g="mouseup clear destroy".split(" "),h=0;h0&&(e--,this.restore()),this},this.redo=function(){return e0?d[e].strokes=c.strokes.slice():(d.push({image:a.toDataURL(),strokes:c.strokes.slice()}),e++)}),this},this.state=function(){return JSON.parse(JSON.stringify(d[e]))},this.restore=function(a){a||(a=d[e]);var c=new Image;c.src=a.image,c.onload=function(){b(this,a.strokes)}},this.init=function(){return Event.remove(document,"keypress",c),Event.add(document,"keypress",c),this.save()},this.destroy=function(){return Event.remove(document,"keypress",c),this.reset()}}var c="sketchable";Sketchable.prototype.plugins.memento=function(a){function d(b){if(!e.options["_bound$"+b])if(e.options["_bound$"+b]=!0,e.options.events&&"function"==typeof e.options.events[b]){var c=e.options.events[b];e.options.events[b]=function(){c.apply(a,arguments),f[b].apply(a,arguments)}}else e.options.events[b]=f[b]}for(var e=a.config(),f={clear:function(a,b){b.memento.reset()},mouseup:function(a,b,c){b.memento.save(c)},destroy:function(a,b){b.memento.destroy()}},g="mouseup clear destroy".split(" "),h=0;h1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function g(a){return a.touches?!1:void n(a)}function h(a){return a.touches?!1:void o(a)}function i(a){return a.touches?!1:void p(a)}function j(a,b){var c=a.target,d=dataBind(c)[q],e=d.options;if(e.multitouch)for(var f=a.changedTouches,g=0;g0&&d.sketch.beginFill(g.graphics.fillStyle).fillCircle(h.x,h.y,g.graphics.firstPointSize).endFill(),d.coords[b]&&d.coords[b].length>0&&(d.strokes.push(d.coords[b].slice()),d.coords[b]=[]),f(b,d,h),"function"==typeof g.events.mousedown&&g.events.mousedown(c,d,a)}}function o(a){var b=a.identifier||0,c=a.target,d=dataBind(c)[q],g=d.options;if(g.interactive&&(g.mouseupMovements&&0!==d.strokes.length||d.sketch.isDrawing)){var h=e(a);if(d.sketch.isDrawing){var i=d.coords[b][d.coords[b].length-1];d.sketch.beginPath().line(i[0],i[1],h.x,h.y).stroke().closePath()}f(b,d,h),"function"==typeof g.events.mousemove&&g.events.mousemove(c,d,a)}}function p(a){var b=a.identifier||0,c=a.target,d=dataBind(c)[q],e=d.options;e.interactive&&(d.sketch.isDrawing=!1,d.strokes.push(d.coords[b].slice()),d.coords[b]=[],"function"==typeof e.events.mouseup&&e.events.mouseup(c,d,a))}var q="sketchable",r=a.document;b.prototype={init:function(a){var a=deepExtend({},b.prototype.defaults,a||{}),c=this.elem,e=dataBind(c)[q];e||(Event.add(c,"mousedown",g),Event.add(c,"mousemove",h),Event.add(c,"mouseup",i),Event.add(c,"touchstart",k),Event.add(c,"touchmove",l),Event.add(c,"touchend",m),d(c,a));var f=new jSketch(c,a.graphics);dataBind(c)[q]=e={strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:f,options:a},"function"==typeof a.events.init&&a.events.init(c,e);for(var j in this.plugins)this.plugins[j](this);return this},config:function(a){var c=this.elem,e=dataBind(c)[q];return a?(e.options=deepExtend({},b.prototype.defaults,a||{}),d(c),this):e},strokes:function(a){var b=this.elem;if(a){var c=dataBind(b)[q];return c.strokes=a,this}var c=dataBind(b)[q];return c.strokes},handler:function(a){var b=this.elem,c=dataBind(b)[q];return a(b,c),this},clear:function(){var a=this.elem,b=dataBind(a)[q],c=b.options;return b.sketch.clear(),b.strokes=[],b.coords={},"function"==typeof c.events.clear&&c.events.clear(a,b),this},reset:function(a){var b=this.elem,c=dataBind(b)[q],a=c.options;return this.destroy().init(a),"function"==typeof a.events.reset&&a.events.reset(b,c),this},destroy:function(){var a=this.elem,b=dataBind(a)[q],c=b.options;return Event.remove(a,"mouseup",i),Event.remove(a,"mousemove",h),Event.remove(a,"mousedown",g),Event.remove(a,"touchstart",k),Event.remove(a,"touchmove",l),Event.remove(a,"touchend",m),dataBind(a)[q]=null,"function"==typeof c.events.destroy&&c.events.destroy(a,b),this}},b.prototype.plugins={},b.prototype.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,filterCoords:!1,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}},a.Sketchable=b}(this); \ No newline at end of file +!function(a){function b(a,b){if(!a)throw new Error("Sketchable requires a DOM element.");return"string"==typeof a&&(a=r.querySelector(a)),this.elem=a,this.init(b)}function c(b){var c=b.getBoundingClientRect(),d=r.body,e=r.documentElement,f=a.pageYOffset||e.scrollTop||d.scrollTop,g=a.pageXOffset||e.scrollLeft||d.scrollLeft,h=e.clientTop||d.clientTop||0,i=e.clientLeft||d.clientLeft||0,j=c.top+f-h,k=c.left+g-i;return{top:Math.round(j),left:Math.round(k)}}function d(a,b){b||(b=dataBind(a)[q].options),b.cssCursors&&(a.style.cursor=b.interactive?"pointer":"not-allowed"),a.onselectstart=function(){return!1}}function e(a){var b=a.target,d=c(b);return{x:Math.round(a.pageX-d.left),y:Math.round(a.pageY-d.top)}}function f(a,b,c){var d=b.coords[a],e=(new Date).getTime();if(b.options.relTimestamps&&(0===b.strokes.length&&0===d.length&&(b.timestamp=e),e-=b.timestamp),d.push([c.x,c.y,e,+b.sketch.isDrawing]),b.options.filterCoords&&d.length>1){var f=d.length-1,g=d[f],h=d[f-1];g[0]==h[0]&&g[1]==h[1]&&d.splice(f,1)}}function g(a){return a.touches?!1:void m(a)}function h(a){return a.touches?!1:void n(a)}function i(a){return a.touches?!1:void o(a)}function j(a){p(a,m),a.preventDefault()}function k(a){p(a,n),a.preventDefault()}function l(a){p(a,o),a.preventDefault()}function m(a){if(Event.isRightClick(a))return!1;var b=a.identifier||0,c=a.target,d=dataBind(c)[q],g=d.options;if(g.interactive){d.sketch.isDrawing=!0;var h=e(a);g.graphics.firstPointSize>0&&d.sketch.beginFill(g.graphics.fillStyle).fillCircle(h.x,h.y,g.graphics.firstPointSize).endFill();var i=d.coords[b];i||(i=[]),i.length>0&&d.strokes.push(i),d.coords[b]=[],f(b,d,h),"function"==typeof g.events.mousedown&&g.events.mousedown(c,d,a)}}function n(a){var b=a.identifier||0;if(elem=a.target,data=dataBind(elem)[q],options=data.options,options.interactive&&(options.mouseupMovements&&0!==data.strokes.length||data.sketch.isDrawing)){var c=e(a),d=data.coords[b],g=d[d.length-1];if(g){var h=data.sketch.beginPath();data.sketch.isDrawing?h.lineStyle(options.graphics.strokeStyle,options.graphics.lineWidth):options.mouseupMovements.visible!==!1&&h.lineStyle(options.mouseupMovements.strokeStyle||"#DDD",options.mouseupMovements.lineWidth||1),h.line(g[0],g[1],c.x,c.y).stroke().closePath()}f(b,data,c),"function"==typeof options.events.mousemove&&options.events.mousemove(elem,data,a)}}function o(a){var b=a.identifier||0;elem=a.target,data=dataBind(elem)[q],options=data.options,options.interactive&&(data.sketch.isDrawing=!1,data.strokes.push(data.coords[b]),data.coords[b]=[],"function"==typeof options.events.mouseup&&options.events.mouseup(elem,data,a))}function p(a,b){var c=a.target,d=dataBind(c)[q],e=d.options;if(e.multitouch)for(var f=a.changedTouches,g=0;g -1) { + // Plugin method. + var actualMethod = locate(api, method); + return actualMethod.apply(this, args); + } else if (api[method]) { + // Instance method. + return api[method].apply(this, args); } else { $.error('Unknown method: ' + method); } @@ -242,7 +250,7 @@ * @type {Object} * @see Sketchable.prototype */ - $.fn.sketchable.api = methods; + $.fn.sketchable.api = api; /** * Plugins store. @@ -263,11 +271,11 @@ * @type {Object} * @example * // The following is the default configuration: - * new Sketchable('canvas', { + * $('canvas').sketchable({ * interactive: true, * mouseupMovements: false, * relTimestamps: false, - * multitouch: false, + * multitouch: true, * cssCursors: true, * filterCoords: false, * // Event hooks. @@ -353,6 +361,18 @@ this.onselectstart = function() { return false }; }; + /** + * @private + */ + function locate(obj, path) { + path = path.split('.'); + for (var i = 0; i < path.length; i++) { + var key = path[i]; + obj = obj[key]; + } + return obj; + } + /** * @private */ @@ -368,11 +388,7 @@ * @private */ function saveMousePos(idx, data, pt) { - // Ensure that coords is properly initialized. - if (!data.coords[idx]) { - data.coords[idx] = []; - } - // Use pointer for easy handling. + // Current coords are already initialized. var coords = data.coords[idx]; var time = (new Date).getTime(); @@ -420,30 +436,6 @@ upHandler(e); }; - /** - * @private - */ - function execTouchEvent(e, callback) { - var elem = $(e.target), data = elem.data(namespace), options = data.options; - if (options.multitouch) { - // Track all fingers. - var touches = e.originalEvent.changedTouches; - for (var i = 0; i < touches.length; i++) { - var touch = touches[i]; - // Add event type and finger ID. - touch.type = e.type; - touch.identifier = i; - callback(touch); - } - } else { - // Track only the current finger. - var touch = e.originalEvent.touches[0]; - touch.type = e.type; - touch.identifier = 0; - callback(touch); - } - }; - /** * @private */ @@ -483,20 +475,21 @@ if (!options.interactive) return; data.sketch.isDrawing = true; + var p = getMousePos(e); // Mark visually 1st point of stroke. if (options.graphics.firstPointSize > 0) { data.sketch.beginFill(options.graphics.fillStyle).fillCircle(p.x, p.y, options.graphics.firstPointSize).endFill(); } + // Ensure that coords is properly initialized. - if (!data.coords[idx]) { - data.coords[idx] = []; - } + var coords = data.coords[idx]; + if (!coords) coords = []; // Don't mix mouseup and mousedown in the same stroke. - if (data.coords[idx].length > 0) { - data.strokes.push(data.coords[idx]); - data.coords[idx] = []; - } + if (coords.length > 0) data.strokes.push(coords); + // In any case, ensure that coords is properly reset/initialized. + data.coords[idx] = []; + saveMousePos(idx, data, p); if (typeof options.events.mousedown === 'function') { @@ -508,20 +501,31 @@ * @private */ function moveHandler(e) { - var idx = e.identifier || 0; - var elem = $(e.target), data = elem.data(namespace), options = data.options; - + var idx = e.identifier || 0, + elem = $(e.target), + data = elem.data(namespace), + options = data.options; + // Exit early if interactivity is disabled. if (!options.interactive) return; - - //if (!options.mouseupMovements && !data.sketch.isDrawing) return; - // This would grab all penup strokes AFTER drawing something on the canvas for the first time. + // Grab penup strokes AFTER drawing something on the canvas for the first time. if ( (!options.mouseupMovements || data.strokes.length === 0) && !data.sketch.isDrawing ) return; var p = getMousePos(e); - if (data.sketch.isDrawing) { - var last = data.coords[idx][ data.coords[idx].length - 1 ]; - data.sketch.beginPath().line(last[0], last[1], p.x, p.y).stroke().closePath(); + + var coords = data.coords[idx]; + var last = coords[coords.length - 1]; + if (last) { + var brush = data.sketch.beginPath(); + if (data.sketch.isDrawing) { + // Style for regular, pendown strokes. + brush.lineStyle(options.graphics.strokeStyle, options.graphics.lineWidth); + } else if (options.mouseupMovements.visible !== false) { + // Style for penup strokes. + brush.lineStyle(options.mouseupMovements.strokeStyle || '#DDD', options.mouseupMovements.lineWidth || 1); + } + brush.line(last[0], last[1], p.x, p.y).stroke().closePath(); } + saveMousePos(idx, data, p); if (typeof options.events.mousemove === 'function') { @@ -533,12 +537,15 @@ * @private */ function upHandler(e) { - var idx = e.identifier || 0; - var elem = $(e.target), data = elem.data(namespace), options = data.options; - + var idx = e.identifier || 0, + elem = $(e.target), + data = elem.data(namespace), + options = data.options; + // Exit early if interactivity is disabled. if (!options.interactive) return; data.sketch.isDrawing = false; + data.strokes.push(data.coords[idx]); data.coords[idx] = []; @@ -547,4 +554,28 @@ } }; + /** + * @private + */ + function execTouchEvent(e, callback) { + var elem = $(e.target), data = elem.data(namespace), options = data.options; + if (options.multitouch) { + // Track all fingers. + var touches = e.originalEvent.changedTouches; + for (var i = 0; i < touches.length; i++) { + var touch = touches[i]; + // Add event type and finger ID. + touch.type = e.type; + touch.identifier = i; + callback(touch); + } + } else { + // Track only the current finger. + var touch = e.originalEvent.touches[0]; + touch.type = e.type; + touch.identifier = 0; + callback(touch); + } + }; + })(jQuery); diff --git a/jquery.sketchable.memento.js b/jquery.sketchable.memento.js index d06dd2c..8f95d8b 100644 --- a/jquery.sketchable.memento.js +++ b/jquery.sketchable.memento.js @@ -1,8 +1,9 @@ /*! - * Memento plugin for jQuery Sketchable | v2.0 | Luis A. Leiva | MIT license + * Memento plugin for jQuery Sketchable | v2.1 | Luis A. Leiva | MIT license */ /* eslint-env browser */ +/* global jQuery */ ;(function($) { // Custom namespace ID, for private data bindind. @@ -12,7 +13,7 @@ * This class implements the Memento pattern * and is part of the {@link $.fn.sketchable.plugins.memento} plugin. * @class - * @version 2.0 + * @version 2.1 * @example * var sketcher = $('canvas').sketchable(); * // This is internally done by the plugin, plus some checks: @@ -24,37 +25,12 @@ var stpos = -1; var self = this; /** + * Update state. + * @param {Image} snapshot Image object. + * @param {Array} strokes Strokes associated with snapshot. * @private */ - function prev() { - if (stpos > 0) { - stpos--; - var snapshot = new Image(); - snapshot.src = stack[stpos].image; - snapshot.onload = function() { - restore(this); - }; - } - }; - /** - * @private - */ - function next() { - if (stpos < stack.length - 1) { - stpos++; - var snapshot = new Image(); - snapshot.src = stack[stpos].image; - snapshot.onload = function() { - restore(this); - }; - } - }; - /** - * Snashot restorer. - * @param {String} snapshot Base64 image. - * @private - */ - function restore(snapshot) { + function draw(snapshot, strokes) { // Manipulate canvas via jQuery sketchable API. // This way, we don't lose default drawing settings et al. $instance.sketchable('handler', function(elem, data) { @@ -64,9 +40,9 @@ data.sketch.clear(); data.sketch.graphics.drawImage(snapshot, 0,0); // Update strokes. - data.strokes = stack[stpos].strokes.slice(); + data.strokes = strokes.slice(); }); - }; + } /** * Key event manager. * - Undo: "Ctrl + Z" @@ -89,14 +65,16 @@ break; } } - }; - + } /** * Goes back to the last saved state, if available. * @return {MementoCanvas} Class instance. */ this.undo = function() { - prev(); + if (stpos > 0) { + stpos--; + this.restore(); + } return this; }; /** @@ -104,7 +82,10 @@ * @return {MementoCanvas} Class instance. */ this.redo = function() { - next(); + if (stpos < stack.length - 1) { + stpos++; + this.restore(); + } return this; }; /** @@ -114,7 +95,8 @@ this.reset = function() { stack = []; stpos = -1; - return this; + // Save blank state afterward. + return this.save(); }; /** * Save current state. @@ -134,6 +116,28 @@ }); return this; }; + /** + * Read current state: `{ image:String, strokes:Array }`. + * @return {Object} + */ + this.state = function() { + // Create a fresh copy of the current state. + return JSON.parse(JSON.stringify(stack[stpos])); + }; + /** + * Restore state. + * @param {Object} state Canvas state: `{ image:String, strokes:Array }`. Default: current state. + * @private + */ + this.restore = function(state) { + if (!state) state = stack[stpos]; + + var snapshot = new Image(); + snapshot.src = state.image; + snapshot.onload = function() { + draw(this, state.strokes); + }; + }; /** * Init instance. Currently just (re)attach key event listeners. * @return {MementoCanvas} Class instance. @@ -141,7 +145,8 @@ this.init = function() { $(document).off('keypress', keyManager); $(document).on('keypress', keyManager); - return this; + // Save blank state to begin with. + return this.save(); }; /** * Destroy instance: reset state and remove key event listeners. @@ -152,11 +157,11 @@ return this.reset(); }; - }; + } /** * Memento plugin constructor for jQuery Sketchable instances. - * @param {Object} $instance - A jQuery Sketchable instance. + * @param {Object} $instance jQuery Sketchable instance. * @memberof $.fn.sketchable.plugins */ $.fn.sketchable.plugins.memento = function($instance) { @@ -165,7 +170,7 @@ var callbacks = { clear: function(elem, data) { - data.memento.reset().save(); + data.memento.reset(); }, mouseup: function(elem, data, evt) { data.memento.save(evt); @@ -176,24 +181,24 @@ }; // A helper function to override user-defined event listeners. - function override(ev) { + function override(evName) { // Flag event override so that it doesn't get fired more than once. - if (config.options[ev + '$bound']) return; - config.options[ev + '$bound'] = true; + if (config.options['_bound$' + evName]) return; + config.options['_bound$' + evName] = true; - if (config.options.events && typeof config.options.events[ev] === 'function') { + if (config.options.events && typeof config.options.events[evName] === 'function') { // User has defined this event, so wrap it. - var fn = config.options.events[ev]; - config.options.events[ev] = function() { + var fn = config.options.events[evName]; + config.options.events[evName] = function() { // Exec original function first, then exec our callback. fn.apply($instance, arguments); - callbacks[ev].apply($instance, arguments); + callbacks[evName].apply($instance, arguments); } } else { // User has not defined this event, so attach our callback. - config.options.events[ev] = callbacks[ev]; + config.options.events[evName] = callbacks[evName]; } - }; + } // Note: the init event is used to create sketchable instances, // therefore it should NOT be overriden. @@ -204,38 +209,67 @@ // Expose public API: all jQuery sketchable instances will have these methods. $.extend($.fn.sketchable.api, { - /** - * Goes back to the previous CANVAS state, if available. - * @memberof $.fn.sketchable - * @example jqueryElem.sketchable('undo'); - */ - undo: function() { - var elem = $(this), data = elem.data(namespace); - data.memento.undo(); - }, - /** - * Goes forward to the previous CANVAS state, if available. - * @memberof $.fn.sketchable - * @example jqueryElem.sketchable('redo'); - */ - redo: function() { - var elem = $(this), data = elem.data(namespace); - data.memento.redo(); - }, - /** - * Save a snapshot of the current CANVAS status. - * @memberof $.fn.sketchable - * @example jqueryElem.sketchable('save'); - */ - save: function() { - var elem = $(this), data = elem.data(namespace); - data.memento.save(); + // Namespace methods to avoid collisions with other plugins. + memento: { + /** + * Goes back to the previous CANVAS state, if available. + * @return {MementoCanvas} + * @memberof $.fn.sketchable + * @example jqueryElem.sketchable('memento.undo'); + */ + undo: function() { + var data = $(this).data(namespace); + return data.memento.undo(); + }, + /** + * Goes forward to the previous CANVAS state, if available. + * @return {MementoCanvas} + * @memberof $.fn.sketchable + * @example jqueryElem.sketchable('memento.redo'); + */ + redo: function() { + var data = $(this).data(namespace); + return data.memento.redo(); + }, + /** + * Save a snapshot of the current CANVAS status. + * @return {MementoCanvas} + * @memberof $.fn.sketchable + * @example jqueryElem.sketchable('memento.save'); + */ + save: function() { + var data = $(this).data(namespace); + return data.memento.save(); + }, + /** + * Read current snapshot of the CANVAS state: `{ image:String, strokes:Array }`. + * @return {Object} + * @memberof Sketchable + * @example var state = jqueryElem.sketchable('memento.state'); + */ + state: function() { + var data = $(this).data(namespace); + return data.memento.state(); + }, + /** + * Restore a snapshot of the CANVAS. + * @param {Object} state + * @param {String} state.image Base64 image. + * @param {Array} state.strokes Associated strokes. + * @return {MementoCanvas} + * @memberof Sketchable + * @example jqueryElem.sketchable('memento.restore', state); + */ + restore: function(state) { + var data = $(this).data(namespace); + return data.memento.restore(state); + } } }); // Initialize plugin here. config.memento = new MementoCanvas($instance); - config.memento.init().save(); + config.memento.init(); }; })(jQuery); diff --git a/sketchable.js b/sketchable.js index ab755b8..c5d2cf8 100644 --- a/sketchable.js +++ b/sketchable.js @@ -1,5 +1,5 @@ /*! - * Sketchable | v2.0 | Luis A. Leiva | MIT license + * Sketchable | v2.1 | Luis A. Leiva | MIT license * A plugin for the jSketch drawing library. */ @@ -21,7 +21,7 @@ * @param {Object} [options] - Configuration (default: {@link Sketchable#defaults}). * @class * @global - * @version 1.9 + * @version 2.1 * @author Luis A. Leiva * @license MIT * @example @@ -84,6 +84,10 @@ timestamp: (new Date).getTime(), // Save a pointer to the drawing canvas (jSketch instance). sketch: sketch, + // Save also a pointer to the Sketchable instance. + // In the jQuery version this is not needed, + // since we access the instance via `$('selector').sketchable('method')`. + instance: this, // Save also a pointer to the given options. options: options }; @@ -112,7 +116,7 @@ config: function(options) { var elem = this.elem, data = dataBind(elem)[namespace]; if (options) { // setter - data.options = deepExtend({}, Sketchable.prototype.defaults, options || {}); + data.options = deepExtend({}, Sketchable.prototype.defaults, data.options, options); postProcess(elem); return this; } else { // getter @@ -253,11 +257,11 @@ * @static * @example * // The following is the default configuration: - * new Sketchable('canvas', { + * new Sketchable('#canvasId', { * interactive: true, * mouseupMovements: false, * relTimestamps: false, - * multitouch: false, + * multitouch: true, * cssCursors: true, * filterCoords: false, * // Event hooks. @@ -292,7 +296,7 @@ * lineJoin: 'round', * miterLimit: 10 * } - * }); + * }; */ Sketchable.prototype.defaults = { // In interactive mode, it's possible to draw via mouse/pen/touch input. @@ -300,7 +304,7 @@ // Indicate whether non-drawing strokes should be registered as well. // Notice that the last mouseUp stroke is never recorded, as the user has already finished drawing. mouseupMovements: false, - // Inidicate whether timestamps should be relative (start at time 0) or absolute (start at Unix epoch). + // Indicate whether timestamps should be relative (start at time 0) or absolute (start at Unix epoch). relTimestamps: false, // Enable multitouch drawing. multitouch: true, @@ -377,11 +381,7 @@ * @private */ function saveMousePos(idx, data, pt) { - // Ensure that coords is properly initialized. - if (!data.coords[idx]) { - data.coords[idx] = []; - } - // Use pointer for easy handling. + // Current coords are already initialized. var coords = data.coords[idx]; var time = (new Date).getTime(); @@ -429,31 +429,6 @@ upHandler(e); }; - /** - * @private - */ - function execTouchEvent(e, callback) { - var elem = e.target, data = dataBind(elem)[namespace], options = data.options; - if (options.multitouch) { - // Track all fingers. - var touches = e.changedTouches; - for (var i = 0; i < touches.length; i++) { - var touch = touches[i]; - // Add event type and finger ID. - touch.type = e.type; - touch.identifier = i; - callback(touch); - } - } else { - // Track only the current finger. - var touch = e.touches[0]; - touch.type = e.type; - touch.identifier = 0; - callback(touch); - } - e.preventDefault(); - }; - /** * @private */ @@ -485,26 +460,29 @@ // Don't handle right clicks. if (Event.isRightClick(e)) return false; - var idx = e.identifier || 0; - var elem = e.target, data = dataBind(elem)[namespace], options = data.options; + var idx = e.identifier || 0, + elem = e.target, + data = dataBind(elem)[namespace], + options = data.options; // Exit early if interactivity is disabled. if (!options.interactive) return; data.sketch.isDrawing = true; + var p = getMousePos(e); // Mark visually 1st point of stroke. if (options.graphics.firstPointSize > 0) { data.sketch.beginFill(options.graphics.fillStyle).fillCircle(p.x, p.y, options.graphics.firstPointSize).endFill(); } + // Ensure that coords is properly initialized. - if (!data.coords[idx]) { - data.coords[idx] = []; - } + var coords = data.coords[idx]; + if (!coords) coords = []; // Don't mix mouseup and mousedown in the same stroke. - if (data.coords[idx].length > 0) { - data.strokes.push(data.coords[idx]); - data.coords[idx] = []; - } + if (coords.length > 0) data.strokes.push(coords); + // In any case, ensure that coords is properly reset/initialized. + data.coords[idx] = []; + saveMousePos(idx, data, p); if (typeof options.events.mousedown === 'function') { @@ -516,19 +494,31 @@ * @private */ function moveHandler(e) { - var idx = e.identifier || 0; - var elem = e.target, data = dataBind(elem)[namespace], options = data.options; - + var idx = e.identifier || 0 + elem = e.target, + data = dataBind(elem)[namespace], + options = data.options; + // Exit early if interactivity is disabled. if (!options.interactive) return; - //if (!options.mouseupMovements && !data.sketch.isDrawing) return; - // This would grab all penup strokes AFTER drawing something on the canvas for the first time. + // Grab penup strokes AFTER drawing something on the canvas for the first time. if ( (!options.mouseupMovements || data.strokes.length === 0) && !data.sketch.isDrawing ) return; var p = getMousePos(e); - if (data.sketch.isDrawing) { - var last = data.coords[idx][ data.coords[idx].length - 1 ]; - data.sketch.beginPath().line(last[0], last[1], p.x, p.y).stroke().closePath(); + + var coords = data.coords[idx]; + var last = coords[coords.length - 1]; + if (last) { + var brush = data.sketch.beginPath(); + if (data.sketch.isDrawing) { + // Style for regular, pendown strokes. + brush.lineStyle(options.graphics.strokeStyle, options.graphics.lineWidth); + } else if (options.mouseupMovements.visible !== false) { + // Style for penup strokes. + brush.lineStyle(options.mouseupMovements.strokeStyle || '#DDD', options.mouseupMovements.lineWidth || 1); + } + brush.line(last[0], last[1], p.x, p.y).stroke().closePath(); } + saveMousePos(idx, data, p); if (typeof options.events.mousemove === 'function') { @@ -540,12 +530,15 @@ * @private */ function upHandler(e) { - var idx = e.identifier || 0; - var elem = e.target, data = dataBind(elem)[namespace], options = data.options; - + var idx = e.identifier || 0 + elem = e.target, + data = dataBind(elem)[namespace], + options = data.options; + // Exit early if interactivity is disabled. if (!options.interactive) return; data.sketch.isDrawing = false; + data.strokes.push(data.coords[idx]); data.coords[idx] = []; @@ -554,6 +547,31 @@ } }; + /** + * @private + */ + function execTouchEvent(e, callback) { + var elem = e.target, data = dataBind(elem)[namespace], options = data.options; + if (options.multitouch) { + // Track all fingers. + var touches = e.changedTouches; + for (var i = 0; i < touches.length; i++) { + var touch = touches[i]; + // Add event type and finger ID. + touch.type = e.type; + touch.identifier = i; + callback(touch); + } + } else { + // Track only the current finger. + var touch = e.touches[0]; + touch.type = e.type; + touch.identifier = 0; + callback(touch); + } + e.preventDefault(); + }; + // Expose. window.Sketchable = Sketchable; diff --git a/sketchable.memento.js b/sketchable.memento.js index 288bc5d..c7683ea 100644 --- a/sketchable.memento.js +++ b/sketchable.memento.js @@ -1,5 +1,5 @@ /*! - * Memento plugin for Sketchable | v2.0 | Luis A. Leiva | MIT license + * Memento plugin for Sketchable | v2.1 | Luis A. Leiva | MIT license */ // XXX: Requires `sketchable.utils.js` to be loaded first. @@ -15,7 +15,7 @@ * This class implements the Memento pattern * and is part of the {@link Sketchable.plugins.memento} plugin. * @class - * @version 2.0 + * @version 2.1 * @example * var sketcher = new Sketchable('canvas'); * // This is internally done by the plugin, plus some checks: @@ -27,38 +27,13 @@ var stpos = -1; var self = this; /** + * Update state. + * @param {Image} snapshot Image object. + * @param {Array} strokes Strokes associated with snapshot. * @private */ - function prev() { - if (stpos > 0) { - stpos--; - var snapshot = new Image(); - snapshot.src = stack[stpos].image; - snapshot.onload = function() { - restore(this); - }; - } - }; - /** - * @private - */ - function next() { - if (stpos < stack.length - 1) { - stpos++; - var snapshot = new Image(); - snapshot.src = stack[stpos].image; - snapshot.onload = function() { - restore(this); - }; - } - }; - /** - * Snashot restorer. - * @param {String} snapshot Base64 image. - * @private - */ - function restore(snapshot) { - // Manipulate canvas via jQuery sketchable API. + function draw(snapshot, strokes) { + // Manipulate canvas via Sketchable API. // This way, we don't lose default drawing settings et al. instance.handler(function(elem, data) { //data.sketch.clear().drawImage(snapshot.src); @@ -67,9 +42,9 @@ data.sketch.clear(); data.sketch.graphics.drawImage(snapshot, 0,0); // Update strokes. - data.strokes = stack[stpos].strokes.slice(); + data.strokes = strokes.slice(); }); - }; + } /** * Key event manager. * - Undo: "Ctrl + Z" @@ -92,14 +67,16 @@ break; } } - }; - + } /** * Goes back to the last saved state, if available. * @return {MementoCanvas} Class instance. */ this.undo = function() { - prev(); + if (stpos > 0) { + stpos--; + this.restore(); + } return this; }; /** @@ -107,7 +84,10 @@ * @return {MementoCanvas} Class instance. */ this.redo = function() { - next(); + if (stpos < stack.length - 1) { + stpos++; + this.restore(); + } return this; }; /** @@ -117,7 +97,8 @@ this.reset = function() { stack = []; stpos = -1; - return this; + // Save blank state afterward. + return this.save(); }; /** * Save current state. @@ -137,6 +118,28 @@ }); return this; }; + /** + * Read current state: `{ image:String, strokes:Array }`. + * @return {Object} + */ + this.state = function() { + // Create a fresh copy of the current state. + return JSON.parse(JSON.stringify(stack[stpos])); + }; + /** + * Restore state. + * @param {Object} state Canvas state: `{ image:String, strokes:Array }`. Default: current state. + * @private + */ + this.restore = function(state) { + if (!state) state = stack[stpos]; + + var snapshot = new Image(); + snapshot.src = state.image; + snapshot.onload = function() { + draw(this, state.strokes); + }; + }; /** * Init instance. Currently just (re)attach key event listeners. * @return {MementoCanvas} Class instance. @@ -144,7 +147,8 @@ this.init = function() { Event.remove(document, 'keypress', keyManager); Event.add(document, 'keypress', keyManager); - return this; + // Save blank state to begin with. + return this.save(); }; /** * Destroy instance: reset state and remove key event listeners. @@ -155,11 +159,11 @@ return this.reset(); }; - }; + } /** - * Memento plugin constructor for jQuery Sketchable instances. - * @param {Object} sketchable - An Sketchable instance. + * Memento plugin constructor for Sketchable instances. + * @param {Object} sketchable Sketchable instance. * @memberof Sketchable#plugins */ Sketchable.prototype.plugins.memento = function(instance) { @@ -168,7 +172,7 @@ var callbacks = { clear: function(elem, data) { - data.memento.reset().save(); + data.memento.reset(); }, mouseup: function(elem, data, evt) { data.memento.save(evt); @@ -179,24 +183,24 @@ }; // A helper function to override user-defined event listeners. - function override(ev) { + function override(evName) { // Flag event override so that it doesn't get fired more than once. - if (config.options[ev + '$bound']) return; - config.options[ev + '$bound'] = true; + if (config.options['_bound$' + evName]) return; + config.options['_bound$' + evName] = true; - if (config.options.events && typeof config.options.events[ev] === 'function') { + if (config.options.events && typeof config.options.events[evName] === 'function') { // User has defined this event, so wrap it. - var fn = config.options.events[ev]; - config.options.events[ev] = function() { + var fn = config.options.events[evName]; + config.options.events[evName] = function() { // Exec original function first, then exec our callback. fn.apply(instance, arguments); - callbacks[ev].apply(instance, arguments); + callbacks[evName].apply(instance, arguments); } } else { // User has not defined this event, so attach our callback. - config.options.events[ev] = callbacks[ev]; + config.options.events[evName] = callbacks[evName]; } - }; + } // Note: the init event is used to create Sketchable instances, // therefore it should NOT be overriden. @@ -206,39 +210,68 @@ } // Expose public API: all Sketchable instances will have these methods. - deepExtend(Sketchable.prototype, { - /** - * Goes back to the previous CANVAS state, if available. - * @memberof Sketchable - * @example sketchableInstance.undo(); - */ - undo: function() { - var elem = this.elem, data = dataBind(elem)[namespace]; - data.memento.undo(); - }, - /** - * Goes forward to the previous CANVAS state, if available. - * @memberof Sketchable - * @example sketchableInstance.redo(); - */ - redo: function() { - var elem = this.elem, data = dataBind(elem)[namespace]; - data.memento.redo(); - }, - /** - * Save a snapshot of the current CANVAS status. - * @memberof Sketchable - * @example sketchableInstance.save(); - */ - save: function() { - var elem = this.elem, data = dataBind(elem)[namespace]; - data.memento.save(); + deepExtend(instance, { + // Namespace methods to avoid collisions with other plugins. + memento: { + /** + * Goes back to the previous CANVAS state, if available. + * @return {MementoCanvas} + * @memberof Sketchable + * @example sketchableInstance.memento.undo(); + */ + undo: function() { + var data = dataBind(instance.elem)[namespace]; + return data.memento.undo(); + }, + /** + * Goes forward to the previous CANVAS state, if available. + * @return {MementoCanvas} + * @memberof Sketchable + * @example sketchableInstance.memento.redo(); + */ + redo: function() { + var data = dataBind(instance.elem)[namespace]; + return data.memento.redo(); + }, + /** + * Save a snapshot of the current CANVAS state. + * @return {MementoCanvas} + * @memberof Sketchable + * @example sketchableInstance.memento.save(); + */ + save: function() { + var data = dataBind(instance.elem)[namespace]; + return data.memento.save(); + }, + /** + * Read current snapshot of the CANVAS state: `{ image:String, strokes:Array }`. + * @return {Object} + * @memberof Sketchable + * @example var state = sketchableInstance.memento.state(); + */ + state: function() { + var data = dataBind(instance.elem)[namespace]; + return data.memento.state(); + }, + /** + * Restore a snapshot of the CANVAS. + * @param {Object} state + * @param {String} state.image Base64 image. + * @param {Array} state.strokes Associated strokes. + * @return {MementoCanvas} + * @memberof Sketchable + * @example sketchableInstance.memento.restore(); + */ + restore: function(state) { + var data = dataBind(instance.elem)[namespace]; + return data.memento.restore(state); + } } }); // Initialize plugin here. config.memento = new MementoCanvas(instance); - config.memento.init().save(); + config.memento.init(); }; })(this); diff --git a/sketchable.utils.js b/sketchable.utils.js index 56a7f2b..5f4fe3e 100644 --- a/sketchable.utils.js +++ b/sketchable.utils.js @@ -1,5 +1,5 @@ -(function(){ - var cache = [0], expando = 'data' + +(new Date); +(function() { + var cache = [0], expando = 'data' + Date.now(); function data(elem) { var cacheIndex = elem[expando], nextCacheIndex = cache.length; @@ -17,9 +17,11 @@ * @example * var elem = document.getElementById('foo'); * // Attach private data to element: - * dataBind(elem)['some-name'] = { value: 42 }; + * dataBind(elem).someName = { value: 42 }; + * dataBind(elem)['other-name'] = { value: 43 }; * // Read private data from element: - * var dat = dataBind(elem)['some-name']; + * var some = dataBind(elem).someName; + * var other = dataBind(elem)['other-name']; */ window.dataBind = data; })();