From 2a1acc3477b1ba97fb8ab941c190efb100feb06d Mon Sep 17 00:00:00 2001 From: Antoine Proulx Date: Mon, 5 Mar 2012 19:01:34 -0500 Subject: [PATCH 1/2] Split the main file into multiple files (one per class). --- kinetic.js => dist/kinetic.js | 0 lib/Circle.js | 35 ++ lib/Container.js | 107 ++++++ lib/GlobalObjet.js | 95 +++++ lib/Group.js | 44 +++ lib/Image.js | 61 ++++ lib/Kinetic.js | 27 ++ lib/Layer.js | 74 ++++ lib/Node.js | 512 +++++++++++++++++++++++++++ lib/Polygon.js | 37 ++ lib/Rect.js | 50 +++ lib/RegularPolygon.js | 52 +++ lib/Shape.js | 156 +++++++++ lib/Stage.js | 629 ++++++++++++++++++++++++++++++++++ lib/Star.js | 52 +++ lib/Text.js | 155 +++++++++ 16 files changed, 2086 insertions(+) rename kinetic.js => dist/kinetic.js (100%) create mode 100644 lib/Circle.js create mode 100644 lib/Container.js create mode 100644 lib/GlobalObjet.js create mode 100644 lib/Group.js create mode 100644 lib/Image.js create mode 100644 lib/Kinetic.js create mode 100644 lib/Layer.js create mode 100644 lib/Node.js create mode 100644 lib/Polygon.js create mode 100644 lib/Rect.js create mode 100644 lib/RegularPolygon.js create mode 100644 lib/Shape.js create mode 100644 lib/Stage.js create mode 100644 lib/Star.js create mode 100644 lib/Text.js diff --git a/kinetic.js b/dist/kinetic.js similarity index 100% rename from kinetic.js rename to dist/kinetic.js diff --git a/lib/Circle.js b/lib/Circle.js new file mode 100644 index 00000000..aa0ac189 --- /dev/null +++ b/lib/Circle.js @@ -0,0 +1,35 @@ +/////////////////////////////////////////////////////////////////////// +// Circle +/////////////////////////////////////////////////////////////////////// +/** + * Circle constructor + * @param {Object} config + */ +Kinetic.Circle = function(config){ + config.drawFunc = function(){ + var canvas = this.getCanvas(); + var context = this.getContext(); + context.beginPath(); + context.arc(0, 0, this.radius, 0, Math.PI * 2, true); + context.closePath(); + this.fillStroke(); + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +/* + * Circle methods + */ +Kinetic.Circle.prototype = { + setRadius: function(radius){ + this.radius = radius; + }, + getRadius: function(){ + return this.radius; + } +}; + +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.Circle, Kinetic.Shape); diff --git a/lib/Container.js b/lib/Container.js new file mode 100644 index 00000000..d5bcdd0b --- /dev/null +++ b/lib/Container.js @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////// +// Container +/////////////////////////////////////////////////////////////////////// + +/** + * Container constructor. Container is the base class for + * Stage, Layer, and Group + */ +Kinetic.Container = function(){ + this.children = []; + this.childrenNames = {}; +}; + +// methods +Kinetic.Container.prototype = { + /** + * set children indices + */ + _setChildrenIndices: function(){ + /* + * if reordering Layers, remove all canvas elements + * from the container except the buffer and backstage canvases + * and then readd all the layers + */ + if (this.className === "Stage") { + var canvases = this.container.childNodes; + var bufferCanvas = canvases[0]; + var backstageCanvas = canvases[1]; + + this.container.innerHTML = ""; + this.container.appendChild(bufferCanvas); + this.container.appendChild(backstageCanvas); + } + + for (var n = 0; n < this.children.length; n++) { + this.children[n].index = n; + + if (this.className === "Stage") { + this.container.appendChild(this.children[n].canvas); + } + } + }, + /** + * recursively traverse the container tree + * and draw the children + * @param {Object} obj + */ + _drawChildren: function(){ + var children = this.children; + for (var n = 0; n < children.length; n++) { + var child = children[n]; + if (child.className === "Shape") { + child._draw(child.getLayer()); + } + else { + child._draw(); + } + } + }, + /** + * get children + */ + getChildren: function(){ + return this.children; + }, + /** + * get node by name + * @param {string} name + */ + getChild: function(name){ + return this.childrenNames[name]; + }, + /** + * add node to container + * @param {Node} child + */ + _add: function(child){ + if (child.name) { + this.childrenNames[child.name] = child; + } + child.id = Kinetic.GlobalObject.idCounter++; + child.index = this.children.length; + child.parent = this; + + this.children.push(child); + }, + /** + * remove child from container + * @param {Node} child + */ + _remove: function(child){ + if (child.name !== undefined) { + this.childrenNames[child.name] = undefined; + } + this.children.splice(child.index, 1); + this._setChildrenIndices(); + child = undefined; + }, + /** + * remove all children + */ + removeChildren: function(){ + while (this.children.length > 0) { + this.remove(this.children[0]); + } + } +}; diff --git a/lib/GlobalObjet.js b/lib/GlobalObjet.js new file mode 100644 index 00000000..65292504 --- /dev/null +++ b/lib/GlobalObjet.js @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////// +// Global Object +/////////////////////////////////////////////////////////////////////// +var Kinetic = {}; +Kinetic.GlobalObject = { + stages: [], + idCounter: 0, + isAnimating: false, + frame: { + time: 0, + timeDiff: 0, + lastTime: 0 + }, + drag: { + moving: false, + node: undefined, + offset: { + x: 0, + y: 0 + } + }, + extend: function(obj1, obj2){ + for (var key in obj2.prototype) { + if (obj2.prototype.hasOwnProperty(key)) { + obj1.prototype[key] = obj2.prototype[key]; + } + } + }, + /* + * animation methods + */ + _isaCanvasAnimating: function(){ + for (var n = 0; n < this.stages.length; n++) { + if (this.stages[n].isAnimating) { + return true; + } + } + return false; + }, + _runFrames: function(){ + for (var n = 0; n < this.stages.length; n++) { + if (this.stages[n].isAnimating) { + this.stages[n].onFrameFunc(this.frame); + } + } + }, + _updateFrameObject: function(){ + var date = new Date(); + var time = date.getTime(); + if (this.frame.lastTime === 0) { + this.frame.lastTime = time; + } + else { + this.frame.timeDiff = time - this.frame.lastTime; + this.frame.lastTime = time; + this.frame.time += this.frame.timeDiff; + } + }, + _animationLoop: function(){ + if (this.isAnimating) { + this._updateFrameObject(); + this._runFrames(); + var that = this; + requestAnimFrame(function(){ + that._animationLoop(); + }); + } + }, + _handleAnimation: function(){ + var that = this; + if (!this.isAnimating && this._isaCanvasAnimating()) { + this.isAnimating = true; + that._animationLoop(); + } + else if (this.isAnimating && !this._isaCanvasAnimating()) { + this.isAnimating = false; + } + } +}; + +/** + * requestAnimFrame shim + * @param {function} callback + */ +window.requestAnimFrame = (function(callback){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback){ + window.setTimeout(callback, 1000 / 60); + }; +})(); + diff --git a/lib/Group.js b/lib/Group.js new file mode 100644 index 00000000..42f64b62 --- /dev/null +++ b/lib/Group.js @@ -0,0 +1,44 @@ +/////////////////////////////////////////////////////////////////////// +// Group +/////////////////////////////////////////////////////////////////////// + +/** + * Group constructor. Group extends Container and Node + * @param {String} name + */ +Kinetic.Group = function(config){ + this.className = "Group"; + + // call super constructors + Kinetic.Container.apply(this, []); + Kinetic.Node.apply(this, [config]); +}; + +Kinetic.Group.prototype = { + /** + * draw children + */ + _draw: function(){ + if (this.visible) { + this._drawChildren(); + } + }, + /** + * add node to group + * @param {Node} child + */ + add: function(child){ + this._add(child); + }, + /** + * remove a child from the group + * @param {Node} child + */ + remove: function(child){ + this._remove(child); + } +}; + +// Extend Container and Node +Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Container); +Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Node); diff --git a/lib/Image.js b/lib/Image.js new file mode 100644 index 00000000..5b55820a --- /dev/null +++ b/lib/Image.js @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////// +// Image +/////////////////////////////////////////////////////////////////////// +/** + * Image constructor + * @param {Object} config + */ +Kinetic.Image = function(config){ + // defaults + if (config.width === undefined) { + config.width = config.image.width; + } + if (config.height === undefined) { + config.height = config.image.height; + } + + config.drawFunc = function(){ + var canvas = this.getCanvas(); + var context = this.getContext(); + context.beginPath(); + context.rect(0, 0, this.width, this.height); + context.closePath(); + this.fillStroke(); + context.drawImage(this.image, 0, 0, this.width, this.height); + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +Kinetic.Image.prototype = { + setImage: function(image){ + this.image = image; + }, + getImage: function(image){ + return this.image; + }, + setWidth: function(width){ + this.width = width; + }, + getWidth: function(){ + return this.width; + }, + setHeight: function(height){ + this.height = height; + }, + getHeight: function(){ + return this.height; + }, + /** + * set width and height + * @param {number} width + * @param {number} height + */ + setSize: function(width, height){ + this.width = width; + this.height = height; + } +}; +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.Image, Kinetic.Shape); diff --git a/lib/Kinetic.js b/lib/Kinetic.js new file mode 100644 index 00000000..fcfa3472 --- /dev/null +++ b/lib/Kinetic.js @@ -0,0 +1,27 @@ +/** + * KineticJS JavaScript Library v3.8.2 + * http://www.kineticjs.com/ + * Copyright 2012, Eric Rowell + * Licensed under the MIT or GPL Version 2 licenses. + * Date: Mar 03 2012 + * + * Copyright (C) 2011 - 2012 by Eric Rowell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ diff --git a/lib/Layer.js b/lib/Layer.js new file mode 100644 index 00000000..d33007ab --- /dev/null +++ b/lib/Layer.js @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////// +// Layer +/////////////////////////////////////////////////////////////////////// +/** + * Layer constructor. Layer extends Container and Node + * @param {string} name + */ +Kinetic.Layer = function(config){ + this.className = "Layer"; + this.canvas = document.createElement('canvas'); + this.context = this.canvas.getContext('2d'); + this.canvas.style.position = 'absolute'; + + // call super constructors + Kinetic.Container.apply(this, []); + Kinetic.Node.apply(this, [config]); +}; +/* + * Layer methods + */ +Kinetic.Layer.prototype = { + /** + * public draw children + */ + draw: function(){ + this._draw(); + }, + /** + * private draw children + */ + _draw: function(){ + this.clear(); + if (this.visible) { + this._drawChildren(); + } + }, + /** + * clear layer + */ + clear: function(){ + var context = this.getContext(); + var canvas = this.getCanvas(); + context.clearRect(0, 0, canvas.width, canvas.height); + }, + /** + * get layer canvas + */ + getCanvas: function(){ + return this.canvas; + }, + /** + * get layer context + */ + getContext: function(){ + return this.context; + }, + /** + * add node to layer + * @param {Node} node + */ + add: function(child){ + this._add(child); + }, + /** + * remove a child from the layer + * @param {Node} child + */ + remove: function(child){ + this._remove(child); + } +}; +// Extend Container and Node +Kinetic.GlobalObject.extend(Kinetic.Layer, Kinetic.Container); +Kinetic.GlobalObject.extend(Kinetic.Layer, Kinetic.Node); diff --git a/lib/Node.js b/lib/Node.js new file mode 100644 index 00000000..07506e1b --- /dev/null +++ b/lib/Node.js @@ -0,0 +1,512 @@ +/////////////////////////////////////////////////////////////////////// +// Node +/////////////////////////////////////////////////////////////////////// +/** + * Node constructor. Node is a base class for the + * Layer, Group, and Shape classes + * @param {Object} name + */ +Kinetic.Node = function(config){ + this.visible = true; + this.isListening = true; + this.name = undefined; + this.alpha = 1; + this.x = 0; + this.y = 0; + this.scale = { + x: 1, + y: 1 + }; + this.rotation = 0; + this.centerOffset = { + x: 0, + y: 0 + }; + this.eventListeners = {}; + this.drag = { + x: false, + y: false + }; + + // set properties from config + if (config) { + for (var key in config) { + // handle special keys + switch (key) { + case "draggable": + this.draggable(config[key]); + break; + case "draggableX": + this.draggableX(config[key]); + break; + case "draggableY": + this.draggableY(config[key]); + break; + case "listen": + this.listen(config[key]); + break; + case "rotationDeg": + this.rotation = config[key] * Math.PI / 180; + break; + default: + this[key] = config[key]; + break; + } + } + } + + // overrides + if (this.centerOffset.x === undefined) { + this.centerOffset.x = 0; + } + if (this.centerOffset.y === undefined) { + this.centerOffset.y = 0; + } +}; + +Kinetic.Node.prototype = { + /** + * bind event to node + * @param {String} typesStr + * @param {function} handler + */ + on: function(typesStr, handler){ + var types = typesStr.split(" "); + /* + * loop through types and attach event listeners to + * each one. eg. "click mouseover.namespace mouseout" + * will create three event bindings + */ + for (var n = 0; n < types.length; n++) { + var type = types[n]; + var event = (type.indexOf('touch') === -1) ? 'on' + type : type; + var parts = event.split("."); + var baseEvent = parts[0]; + var name = parts.length > 1 ? parts[1] : ""; + + if (!this.eventListeners[baseEvent]) { + this.eventListeners[baseEvent] = []; + } + + this.eventListeners[baseEvent].push({ + name: name, + handler: handler + }); + } + }, + /** + * unbind event to node + * @param {String} typesStr + */ + off: function(typesStr){ + var types = typesStr.split(" "); + + for (var n = 0; n < types.length; n++) { + var type = types[n]; + var event = (type.indexOf('touch') === -1) ? 'on' + type : type; + var parts = event.split("."); + var baseEvent = parts[0]; + + if (this.eventListeners[baseEvent] && parts.length > 1) { + var name = parts[1]; + + for (var i = 0; i < this.eventListeners[baseEvent].length; i++) { + if (this.eventListeners[baseEvent][i].name === name) { + this.eventListeners[baseEvent].splice(i, 1); + if (this.eventListeners[baseEvent].length === 0) { + this.eventListeners[baseEvent] = undefined; + } + break; + } + } + } + else { + this.eventListeners[baseEvent] = undefined; + } + } + }, + /** + * show node + */ + show: function(){ + this.visible = true; + }, + /** + * hide node + */ + hide: function(){ + this.visible = false; + }, + /** + * get zIndex + */ + getZIndex: function(){ + return this.index; + }, + /** + * set node scale + * @param {number} scaleX + * @param {number} scaleY + */ + setScale: function(scaleX, scaleY){ + if (scaleY) { + this.scale.x = scaleX; + this.scale.y = scaleY; + } + else { + this.scale.x = scaleX; + this.scale.y = scaleX; + } + }, + /** + * get scale + */ + getScale: function(){ + return this.scale; + }, + /** + * set node position + * @param {number} x + * @param {number} y + */ + setPosition: function(x, y){ + this.x = x; + this.y = y; + }, + /** + * get node position relative to container + */ + getPosition: function(){ + return { + x: this.x, + y: this.y + }; + }, + /** + * get absolute position relative to stage + */ + getAbsolutePosition: function(){ + var x = this.x; + var y = this.y; + var parent = this.getParent(); + while (parent.className !== "Stage") { + x += parent.x; + y += parent.y; + parent = parent.parent; + } + return { + x: x, + y: y + }; + }, + /** + * move node + * @param {number} x + * @param {number} y + */ + move: function(x, y){ + this.x += x; + this.y += y; + }, + /** + * set node rotation + * @param {number} theta + */ + setRotation: function(theta){ + this.rotation = theta; + }, + /** + * set rotation using degrees + * @param {number} deg + */ + setRotationDeg: function(deg){ + this.rotation = (deg * Math.PI / 180); + }, + /** + * get rotation + */ + getRotation: function(){ + return this.rotation; + }, + /** + * get rotation in degrees + */ + getRotationDeg: function(){ + return this.rotation * 180 / Math.PI; + }, + /** + * rotate node + * @param {number} theta + */ + rotate: function(theta){ + this.rotation += theta; + }, + /** + * rotate node using degrees + * @param {number} deg + */ + rotateDeg: function(deg){ + this.rotation += (deg * Math.PI / 180); + }, + /** + * listen or don't listen to events + * @param {boolean} isListening + */ + listen: function(isListening){ + this.isListening = isListening; + }, + /** + * move node to top + */ + moveToTop: function(){ + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.push(this); + this.parent._setChildrenIndices(); + }, + /** + * move node up + */ + moveUp: function(){ + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(index + 1, 0, this); + this.parent._setChildrenIndices(); + }, + /** + * move node down + */ + moveDown: function(){ + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index - 1, 0, this); + this.parent._setChildrenIndices(); + } + }, + /** + * move node to bottom + */ + moveToBottom: function(){ + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.unshift(this); + this.parent._setChildrenIndices(); + }, + /** + * set zIndex + * @param {int} index + */ + setZIndex: function(zIndex){ + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(zIndex, 0, this); + this.parent._setChildrenIndices(); + }, + /** + * set alpha + * @param {Object} alpha + */ + setAlpha: function(alpha){ + this.alpha = alpha; + }, + /** + * get alpha + */ + getAlpha: function(){ + return this.alpha; + }, + /** + * get absolute alpha + */ + getAbsoluteAlpha: function(){ + var absAlpha = 1; + var node = this; + // traverse upwards + while (node.className !== "Stage") { + absAlpha *= node.alpha; + node = node.parent; + } + return absAlpha; + }, + /** + * initialize drag and drop + */ + _initDrag: function(){ + var go = Kinetic.GlobalObject; + var that = this; + this.on("mousedown.initdrag touchstart.initdrag", function(evt){ + var stage = that.getStage(); + var pos = stage.getUserPosition(); + + if (pos) { + go.drag.node = that; + go.drag.offset.x = pos.x - that.x; + go.drag.offset.y = pos.y - that.y; + } + }); + }, + /** + * remove drag and drop event listener + */ + _dragCleanup: function(){ + if (!this.drag.x && !this.drag.y) { + this.off("mousedown.initdrag"); + this.off("touchstart.initdrag"); + } + }, + /** + * enable/disable drag and drop for box x and y direction + * @param {boolean} setDraggable + */ + draggable: function(setDraggable){ + if (setDraggable) { + var needInit = !this.drag.x && !this.drag.y; + this.drag.x = true; + this.drag.y = true; + + if (needInit) { + this._initDrag(); + } + } + else { + this.drag.x = false; + this.drag.y = false; + this._dragCleanup(); + } + }, + /** + * enable/disable drag and drop for x only + * @param {boolean} setDraggable + */ + draggableX: function(setDraggable){ + if (setDraggable) { + var needInit = !this.drag.x && !this.drag.y; + this.drag.x = true; + if (needInit) { + this._initDrag(); + } + } + else { + this.drag.x = false; + this._dragCleanup(); + } + }, + /** + * enable/disable drag and drop for y only + * @param {boolean} setDraggable + */ + draggableY: function(setDraggable){ + if (setDraggable) { + var needInit = !this.drag.x && !this.drag.y; + this.drag.y = true; + if (needInit) { + this._initDrag(); + } + } + else { + this.drag.y = false; + this._dragCleanup(); + } + }, + /** + * determine if node is currently in drag mode + */ + isDragging: function(){ + var go = Kinetic.GlobalObject; + return go.drag.node !== undefined && go.drag.node.id === this.id && go.drag.moving; + }, + /** + * handle node events + * @param {string} eventType + * @param {Event} evt + */ + _handleEvents: function(eventType, evt){ + // generic events handler + function handle(obj){ + var el = obj.eventListeners; + if (el[eventType]) { + var events = el[eventType]; + for (var i = 0; i < events.length; i++) { + events[i].handler.apply(obj, [evt]); + } + } + + if (obj.parent.className !== "Stage") { + handle(obj.parent); + } + } + /* + * simulate bubbling by handling node events + * first, followed by group events, followed + * by layer events + */ + handle(this); + }, + /** + * move node to another container + * @param {Layer} newLayer + */ + moveTo: function(newContainer){ + var parent = this.parent; + // remove from parent's children + parent.children.splice(this.index, 1); + parent._setChildrenIndices(); + + // add to new parent + newContainer.children.push(this); + this.index = newContainer.children.length - 1; + this.parent = newContainer; + newContainer._setChildrenIndices(); + + // update children hashes + if (this.name) { + parent.childrenNames[this.name] = undefined; + newContainer.childrenNames[this.name] = this; + } + }, + /** + * get parent + */ + getParent: function(){ + return this.parent; + }, + /** + * get node's layer + */ + getLayer: function(){ + if (this.className === 'Layer') { + return this; + } + else { + return this.getParent().getLayer(); + } + }, + /** + * get stage + */ + getStage: function(){ + return this.getParent().getStage(); + }, + /** + * get name + */ + getName: function(){ + return this.name; + }, + /** + * set center offset + * @param {number} x + * @param {number} y + */ + setCenterOffset: function(x, y){ + this.centerOffset.x = x; + this.centerOffset.y = y; + }, + /** + * get center offset + */ + getCenterOffset: function(){ + return this.centerOffset; + } +}; diff --git a/lib/Polygon.js b/lib/Polygon.js new file mode 100644 index 00000000..bb303865 --- /dev/null +++ b/lib/Polygon.js @@ -0,0 +1,37 @@ +/////////////////////////////////////////////////////////////////////// +// Polygon +/////////////////////////////////////////////////////////////////////// +/** + * Polygon constructor + * @param {Object} config + */ +Kinetic.Polygon = function(config){ + config.drawFunc = function(){ + var context = this.getContext(); + context.beginPath(); + context.moveTo(this.points[0].x, this.points[0].y); + for (var n = 1; n < this.points.length; n++) { + context.lineTo(this.points[n].x, this.points[n].y); + } + context.closePath(); + this.fillStroke(); + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +/* + * Polygon methods + */ +Kinetic.Polygon.prototype = { + setPoints: function(points){ + this.points = points; + }, + getPoints: function(){ + return this.points; + } +}; + +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.Polygon, Kinetic.Shape); diff --git a/lib/Rect.js b/lib/Rect.js new file mode 100644 index 00000000..c8ba447f --- /dev/null +++ b/lib/Rect.js @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////// +// Rect +/////////////////////////////////////////////////////////////////////// +/** + * Rect constructor + * @param {Object} config + */ +Kinetic.Rect = function(config){ + config.drawFunc = function(){ + var canvas = this.getCanvas(); + var context = this.getContext(); + context.beginPath(); + context.rect(0, 0, this.width, this.height); + context.closePath(); + this.fillStroke(); + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +/* + * Rect methods + */ +Kinetic.Rect.prototype = { + setWidth: function(width){ + this.width = width; + }, + getWidth: function(){ + return this.width; + }, + setHeight: function(height){ + this.height = height; + }, + getHeight: function(){ + return this.height; + }, + /** + * set width and height + * @param {number} width + * @param {number} height + */ + setSize: function(width, height){ + this.width = width; + this.height = height; + } +}; + +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.Rect, Kinetic.Shape); diff --git a/lib/RegularPolygon.js b/lib/RegularPolygon.js new file mode 100644 index 00000000..d21d63a9 --- /dev/null +++ b/lib/RegularPolygon.js @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////// +// RegularPolygon +/////////////////////////////////////////////////////////////////////// +/** + * Polygon constructor + * @param {Object} config + */ +Kinetic.RegularPolygon = function(config){ + config.drawFunc = function(){ + var context = this.getContext(); + context.beginPath(); + context.moveTo(0, 0 - this.radius); + + for (var n = 1; n < this.sides; n++) { + var x = this.radius * Math.sin(n * 2 * Math.PI / this.sides); + var y = -1 * this.radius * Math.cos(n * 2 * Math.PI / this.sides); + context.lineTo(x, y); + } + context.closePath(); + this.fillStroke(); + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +/* + * RegularPolygon methods + */ +Kinetic.RegularPolygon.prototype = { + setPoints: function(points){ + this.points = points; + }, + getPoints: function(){ + return this.points; + }, + setRadius: function(radius){ + this.radius = radius; + }, + getRadius: function(){ + return this.radius; + }, + setSides: function(sides){ + this.sides = sides; + }, + getSides: function(){ + return this.sides; + } +}; + +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.RegularPolygon, Kinetic.Shape); diff --git a/lib/Shape.js b/lib/Shape.js new file mode 100644 index 00000000..e33ea266 --- /dev/null +++ b/lib/Shape.js @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////// +// Shape +/////////////////////////////////////////////////////////////////////// +/** + * Shape constructor + * @param {Object} config + */ +Kinetic.Shape = function(config){ + this.className = "Shape"; + + // defaults + if (config.stroke !== undefined || config.strokeWidth !== undefined) { + if (config.stroke === undefined) { + config.stroke = "black"; + } + else if (config.strokeWidth === undefined) { + config.strokeWidth = 2; + } + } + + // required + this.drawFunc = config.drawFunc; + + // call super constructor + Kinetic.Node.apply(this, [config]); +}; +/* + * Shape methods + */ +Kinetic.Shape.prototype = { + /** + * get shape temp layer context + */ + getContext: function(){ + return this.tempLayer.getContext(); + }, + /** + * get shape temp layer canvas + */ + getCanvas: function(){ + return this.tempLayer.getCanvas(); + }, + /** + * draw shape + * @param {Layer} layer + */ + _draw: function(layer){ + if (this.visible) { + var stage = layer.getStage(); + var context = layer.getContext(); + + var family = []; + + family.unshift(this); + var parent = this.parent; + while (parent.className !== "Stage") { + family.unshift(parent); + parent = parent.parent; + } + + // children transforms + for (var n = 0; n < family.length; n++) { + var obj = family[n]; + + context.save(); + if (obj.x !== 0 || obj.y !== 0) { + context.translate(obj.x, obj.y); + } + if (obj.centerOffset.x !== 0 || obj.centerOffset.y !== 0) { + context.translate(obj.centerOffset.x, obj.centerOffset.y); + } + if (obj.rotation !== 0) { + context.rotate(obj.rotation); + } + if (obj.scale.x !== 1 || obj.scale.y !== 1) { + context.scale(obj.scale.x, obj.scale.y); + } + if (obj.centerOffset.x !== 0 || obj.centerOffset.y !== 0) { + context.translate(-1 * obj.centerOffset.x, -1 * obj.centerOffset.y); + } + if (obj.getAbsoluteAlpha() !== 1) { + context.globalAlpha = obj.getAbsoluteAlpha(); + } + } + + // stage transform + context.save(); + if (stage && (stage.scale.x !== 1 || stage.scale.y !== 1)) { + context.scale(stage.scale.x, stage.scale.y); + } + + this.tempLayer = layer; + this.drawFunc.call(this); + + // children restore + for (var i = 0; i < family.length; i++) { + context.restore(); + } + + // stage restore + context.restore(); + } + }, + fillStroke: function(){ + var context = this.getContext(); + + if (this.fill !== undefined) { + context.fillStyle = this.fill; + context.fill(); + } + if (this.stroke !== undefined) { + context.lineWidth = this.strokeWidth === undefined ? 1 : this.strokeWidth; + context.strokeStyle = this.stroke; + context.stroke(); + } + }, + /* + * set fill which can be a color, gradient object, + * or pattern object + */ + setFill: function(fill){ + this.fill = fill; + }, + /* + * get fill + */ + getFill: function(){ + return this.fill; + }, + /* + * set stroke color + */ + setStroke: function(stroke){ + this.stroke = stroke; + }, + /* + * get stroke + */ + getStroke: function(){ + return this.stroke; + }, + /* + * set stroke width + */ + setStrokeWidth: function(strokeWidth){ + this.strokeWidth = strokeWidth; + }, + /* + * get stroke width + */ + getStrokeWidth: function(){ + return this.strokeWidth; + } +}; +// extend Node +Kinetic.GlobalObject.extend(Kinetic.Shape, Kinetic.Node); diff --git a/lib/Stage.js b/lib/Stage.js new file mode 100644 index 00000000..59d3d893 --- /dev/null +++ b/lib/Stage.js @@ -0,0 +1,629 @@ +/////////////////////////////////////////////////////////////////////// +// Stage +/////////////////////////////////////////////////////////////////////// +/** + * Stage constructor. Stage extends Container + * @param {String} containerId + * @param {int} width + * @param {int} height + */ +Kinetic.Stage = function(cont, width, height){ + this.className = "Stage"; + this.container = typeof cont === "string" ? document.getElementById(cont) : cont; + this.width = width; + this.height = height; + this.scale = { + x: 1, + y: 1 + }; + this.dblClickWindow = 400; + this.targetShape = undefined; + this.clickStart = false; + + // desktop flags + this.mousePos = undefined; + this.mouseDown = false; + this.mouseUp = false; + + // mobile flags + this.touchPos = undefined; + this.touchStart = false; + this.touchEnd = false; + + /* + * Layer roles + * + * buffer - canvas compositing + * backstage - path detection + */ + this.bufferLayer = new Kinetic.Layer(); + this.backstageLayer = new Kinetic.Layer(); + + // set parents + this.bufferLayer.parent = this; + this.backstageLayer.parent = this; + + // customize back stage context + var backstageLayer = this.backstageLayer; + this._stripLayer(backstageLayer); + + this.bufferLayer.getCanvas().style.display = 'none'; + this.backstageLayer.getCanvas().style.display = 'none'; + + // add buffer layer + this.bufferLayer.canvas.width = this.width; + this.bufferLayer.canvas.height = this.height; + this.container.appendChild(this.bufferLayer.canvas); + + // add backstage layer + this.backstageLayer.canvas.width = this.width; + this.backstageLayer.canvas.height = this.height; + this.container.appendChild(this.backstageLayer.canvas); + + this._listen(); + this._prepareDrag(); + + // add stage to global object + var stages = Kinetic.GlobalObject.stages; + stages.push(this); + + // set stage id + this.id = Kinetic.GlobalObject.idCounter++; + + // animation support + this.isAnimating = false; + this.onFrameFunc = undefined; + + // call super constructor + Kinetic.Container.apply(this, []); +}; + +/* + * Stage methods + */ +Kinetic.Stage.prototype = { + /** + * sets onFrameFunc for animation + */ + onFrame: function(func){ + this.onFrameFunc = func; + }, + /** + * start animation + */ + start: function(){ + this.isAnimating = true; + Kinetic.GlobalObject._handleAnimation(); + }, + /** + * stop animation + */ + stop: function(){ + this.isAnimating = false; + Kinetic.GlobalObject._handleAnimation(); + }, + /** + * draw children + */ + draw: function(){ + this._drawChildren(); + }, + /** + * disable layer rendering + * @param {Layer} layer + */ + _stripLayer: function(layer){ + layer.context.stroke = function(){ + }; + layer.context.fill = function(){ + }; + layer.context.fillRect = function(x, y, width, height){ + layer.context.rect(x, y, width, height); + }; + layer.context.strokeRect = function(x, y, width, height){ + layer.context.rect(x, y, width, height); + }; + layer.context.drawImage = function(){ + }; + layer.context.fillText = function(){ + }; + layer.context.strokeText = function(){ + }; + }, + /** + * end drag and drop + */ + _endDrag: function(evt){ + var go = Kinetic.GlobalObject; + if (go.drag.node) { + if (go.drag.moving) { + go.drag.moving = false; + go.drag.node._handleEvents("ondragend", evt); + } + } + go.drag.node = undefined; + }, + /** + * prepare drag and drop + */ + _prepareDrag: function(){ + var that = this; + + this.on("mousemove touchmove", function(evt){ + var go = Kinetic.GlobalObject; + if (go.drag.node) { + var pos = that.getUserPosition(); + if (go.drag.node.drag.x) { + go.drag.node.x = pos.x - go.drag.offset.x; + } + if (go.drag.node.drag.y) { + go.drag.node.y = pos.y - go.drag.offset.y; + } + go.drag.node.getLayer().draw(); + + if (!go.drag.moving) { + go.drag.moving = true; + // execute dragstart events if defined + go.drag.node._handleEvents("ondragstart", evt); + } + // execute user defined ondragmove if defined + go.drag.node._handleEvents("ondragmove", evt); + } + }, false); + + this.on("mouseup touchend mouseout", function(evt){ + that._endDrag(evt); + }); + }, + /** + * set stage size + * @param {int} width + * @param {int} height + */ + setSize: function(width, height){ + var layers = this.children; + for (var n = 0; n < layers.length; n++) { + var layer = layers[n]; + layer.getCanvas().width = width; + layer.getCanvas().height = height; + layer.draw(); + } + + // set stage dimensions + this.width = width; + this.height = height; + + // set buffer layer and backstage layer sizes + this.bufferLayer.getCanvas().width = width; + this.bufferLayer.getCanvas().height = height; + this.backstageLayer.getCanvas().width = width; + this.backstageLayer.getCanvas().height = height; + }, + /** + * set stage scale + * @param {int} scaleX + * @param {int} scaleY + */ + setScale: function(scaleX, scaleY){ + var oldScaleX = this.scale.x; + var oldScaleY = this.scale.y; + + if (scaleY) { + this.scale.x = scaleX; + this.scale.y = scaleY; + } + else { + this.scale.x = scaleX; + this.scale.y = scaleX; + } + + /* + * scale all shape positions + */ + var layers = this.children; + var that = this; + function scaleChildren(children){ + for (var i = 0; i < children.length; i++) { + var child = children[i]; + child.x *= that.scale.x / oldScaleX; + child.y *= that.scale.y / oldScaleY; + if (child.children) { + scaleChildren(child.children); + } + } + } + + scaleChildren(layers); + }, + /** + * get scale + */ + getScale: function(){ + return this.scale; + }, + /** + * clear all layers + */ + clear: function(){ + var layers = this.children; + for (var n = 0; n < layers.length; n++) { + layers[n].clear(); + } + }, + /** + * creates a composite data URL and passes it to a callback + * @param {function} callback + */ + toDataURL: function(callback){ + var bufferLayer = this.bufferLayer; + var bufferContext = bufferLayer.getContext(); + var layers = this.children; + + function addLayer(n){ + var dataURL = layers[n].getCanvas().toDataURL(); + var imageObj = new Image(); + imageObj.onload = function(){ + bufferContext.drawImage(this, 0, 0); + n++; + if (n < layers.length) { + addLayer(n); + } + else { + callback(bufferLayer.getCanvas().toDataURL()); + } + }; + imageObj.src = dataURL; + } + + + bufferLayer.clear(); + addLayer(0); + }, + /** + * remove layer from stage + * @param {Layer} layer + */ + remove: function(layer){ + // remove layer canvas from dom + this.container.removeChild(layer.canvas); + + this._remove(layer); + }, + /** + * bind event listener to stage (which is essentially + * the container DOM) + * @param {string} type + * @param {function} handler + */ + on: function(typesStr, handler){ + var types = typesStr.split(" "); + for (var n = 0; n < types.length; n++) { + var baseEvent = types[n]; + this.container.addEventListener(baseEvent, handler, false); + } + }, + /** + * add layer to stage + * @param {Layer} layer + */ + add: function(layer){ + if (layer.name) { + this.childrenNames[layer.name] = layer; + } + layer.canvas.width = this.width; + layer.canvas.height = this.height; + this._add(layer); + + // draw layer and append canvas to container + layer.draw(); + this.container.appendChild(layer.canvas); + }, + /** + * handle incoming event + * @param {Event} evt + */ + _handleEvent: function(evt){ + if (!evt) { + evt = window.event; + } + + this._setMousePosition(evt); + this._setTouchPosition(evt); + + var backstageLayer = this.backstageLayer; + var backstageLayerContext = backstageLayer.getContext(); + var that = this; + + backstageLayer.clear(); + + /* + * loop through layers. If at any point an event + * is triggered, n is set to -1 which will break out of the + * three nested loops + */ + var targetFound = false; + + function detectEvent(shape){ + shape._draw(backstageLayer); + var pos = that.getUserPosition(); + var el = shape.eventListeners; + + if (that.targetShape && shape.id === that.targetShape.id) { + targetFound = true; + } + + if (shape.visible && pos !== undefined && backstageLayerContext.isPointInPath(pos.x, pos.y)) { + // handle onmousedown + if (that.mouseDown) { + that.mouseDown = false; + that.clickStart = true; + shape._handleEvents("onmousedown", evt); + return true; + } + // handle onmouseup & onclick + else if (that.mouseUp) { + that.mouseUp = false; + shape._handleEvents("onmouseup", evt); + + // detect if click or double click occurred + if (that.clickStart) { + /* + * if dragging and dropping, don't fire click or dbl click + * event + */ + if ((!go.drag.moving) || !go.drag.node) { + shape._handleEvents("onclick", evt); + + if (shape.inDoubleClickWindow) { + shape._handleEvents("ondblclick", evt); + } + shape.inDoubleClickWindow = true; + setTimeout(function(){ + shape.inDoubleClickWindow = false; + }, that.dblClickWindow); + } + } + return true; + } + + // handle touchstart + else if (that.touchStart) { + that.touchStart = false; + shape._handleEvents("touchstart", evt); + + if (el.ondbltap && shape.inDoubleClickWindow) { + var events = el.ondbltap; + for (var i = 0; i < events.length; i++) { + events[i].handler.apply(shape, [evt]); + } + } + + shape.inDoubleClickWindow = true; + + setTimeout(function(){ + shape.inDoubleClickWindow = false; + }, that.dblClickWindow); + return true; + } + + // handle touchend + else if (that.touchEnd) { + that.touchEnd = false; + shape._handleEvents("touchend", evt); + return true; + } + + // handle touchmove + else if (el.touchmove) { + shape._handleEvents("touchmove", evt); + return true; + } + + //this condition is used to identify a new target shape. + else if (!that.targetShape || (!targetFound && shape.id !== that.targetShape.id)) { + /* + * check if old target has an onmouseout event listener + */ + if (that.targetShape) { + var oldEl = that.targetShape.eventListeners; + if (oldEl) { + that.targetShape._handleEvents("onmouseout", evt); + } + } + + // set new target shape + that.targetShape = shape; + + // handle onmouseover + shape._handleEvents("onmouseover", evt); + return true; + } + + // handle onmousemove + else { + shape._handleEvents("onmousemove", evt); + return true; + } + } + // handle mouseout condition + else if (that.targetShape && that.targetShape.id === shape.id) { + that.targetShape = undefined; + shape._handleEvents("onmouseout", evt); + return true; + } + + return false; + } + + function traverseChildren(obj){ + var children = obj.children; + // propapgate backwards through children + for (var i = children.length - 1; i >= 0; i--) { + var child = children[i]; + if (child.className === "Shape") { + var exit = detectEvent(child); + if (exit) { + return true; + } + } + else { + traverseChildren(child); + } + } + + return false; + } + + var go = Kinetic.GlobalObject; + if (go.drag.node === undefined) { + for (var n = this.children.length - 1; n >= 0; n--) { + var layer = this.children[n]; + if (layer.visible && n >= 0 && layer.isListening) { + if (traverseChildren(layer)) { + n = -1; + } + } + } + } + }, + /** + * begin listening for events by adding event handlers + * to the container + */ + _listen: function(){ + var that = this; + + // desktop events + this.container.addEventListener("mousedown", function(evt){ + that.mouseDown = true; + that._handleEvent(evt); + }, false); + + this.container.addEventListener("mousemove", function(evt){ + that.mouseUp = false; + that.mouseDown = false; + that._handleEvent(evt); + }, false); + + this.container.addEventListener("mouseup", function(evt){ + that.mouseUp = true; + that.mouseDown = false; + that._handleEvent(evt); + + that.clickStart = false; + }, false); + + this.container.addEventListener("mouseover", function(evt){ + that._handleEvent(evt); + }, false); + + this.container.addEventListener("mouseout", function(evt){ + that.mousePos = undefined; + }, false); + // mobile events + this.container.addEventListener("touchstart", function(evt){ + evt.preventDefault(); + that.touchStart = true; + that._handleEvent(evt); + }, false); + + this.container.addEventListener("touchmove", function(evt){ + evt.preventDefault(); + that._handleEvent(evt); + }, false); + + this.container.addEventListener("touchend", function(evt){ + evt.preventDefault(); + that.touchEnd = true; + that._handleEvent(evt); + }, false); + }, + /** + * get mouse position for desktop apps + * @param {Event} evt + */ + getMousePosition: function(evt){ + return this.mousePos; + }, + /** + * get touch position for mobile apps + * @param {Event} evt + */ + getTouchPosition: function(evt){ + return this.touchPos; + }, + /** + * get user position (mouse position or touch position) + * @param {Event} evt + */ + getUserPosition: function(evt){ + return this.getTouchPosition() || this.getMousePosition(); + }, + /** + * set mouse positon for desktop apps + * @param {Event} evt + */ + _setMousePosition: function(evt){ + var mouseX = evt.clientX - this._getContainerPosition().left + window.pageXOffset; + var mouseY = evt.clientY - this._getContainerPosition().top + window.pageYOffset; + this.mousePos = { + x: mouseX, + y: mouseY + }; + }, + /** + * set touch position for mobile apps + * @param {Event} evt + */ + _setTouchPosition: function(evt){ + if (evt.touches !== undefined && evt.touches.length === 1) {// Only deal with + // one finger + var touch = evt.touches[0]; + // Get the information for finger #1 + var touchX = touch.clientX - this._getContainerPosition().left + window.pageXOffset; + var touchY = touch.clientY - this._getContainerPosition().top + window.pageYOffset; + + this.touchPos = { + x: touchX, + y: touchY + }; + } + }, + /** + * get container position + */ + _getContainerPosition: function(){ + var obj = this.container; + var top = 0; + var left = 0; + while (obj && obj.tagName !== "BODY") { + top += obj.offsetTop; + left += obj.offsetLeft; + obj = obj.offsetParent; + } + return { + top: top, + left: left + }; + }, + /** + * get container DOM element + */ + getContainer: function(){ + return this.container; + }, + /** + * get stage + */ + getStage: function(){ + return this; + }, + /** + * get target shape + */ + getTargetShape: function(){ + return this.targetShape; + } +}; +// extend Container +Kinetic.GlobalObject.extend(Kinetic.Stage, Kinetic.Container); diff --git a/lib/Star.js b/lib/Star.js new file mode 100644 index 00000000..85bddb9a --- /dev/null +++ b/lib/Star.js @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////// +// Star +/////////////////////////////////////////////////////////////////////// +/** + * Star constructor + * @param {Object} config + */ +Kinetic.Star = function(config){ + config.drawFunc = function(){ + var context = this.getContext(); + context.beginPath(); + context.moveTo(0, 0 - this.outerRadius); + + for (var n = 1; n < this.points * 2; n++) { + var radius = n % 2 === 0 ? this.outerRadius : this.innerRadius; + var x = radius * Math.sin(n * Math.PI / this.points); + var y = -1 * radius * Math.cos(n * Math.PI / this.points); + context.lineTo(x, y); + } + context.closePath(); + this.fillStroke(); + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +/* + * Star methods + */ +Kinetic.Star.prototype = { + setPoints: function(points){ + this.points = points; + }, + getPoints: function(){ + return this.points; + }, + setOuterRadius: function(radius){ + this.outerRadius = radius; + }, + getOuterRadius: function(){ + return this.outerRadius; + }, + setInnerRadius: function(radius){ + this.innerRadius = radius; + }, + getInnerRadius: function(){ + return this.innerRadius; + } +}; +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.Star, Kinetic.Shape); diff --git a/lib/Text.js b/lib/Text.js new file mode 100644 index 00000000..57c9c879 --- /dev/null +++ b/lib/Text.js @@ -0,0 +1,155 @@ +/////////////////////////////////////////////////////////////////////// +// Text +/////////////////////////////////////////////////////////////////////// +/** + * Text constructor + * @param {Object} config + */ +Kinetic.Text = function(config){ + + /* + * defaults + */ + if (config.textStroke !== undefined || config.textStrokeWidth !== undefined) { + if (config.textStroke === undefined) { + config.textStroke = "black"; + } + else if (config.textStrokeWidth === undefined) { + config.textStrokeWidth = 2; + } + } + if (config.align === undefined) { + config.align = "left"; + } + if (config.verticalAlign === undefined) { + config.verticalAlign = "top"; + } + if (config.padding === undefined) { + config.padding = 0; + } + + config.drawFunc = function(){ + var canvas = this.getCanvas(); + var context = this.getContext(); + context.font = this.fontSize + "pt " + this.fontFamily; + context.textBaseline = "middle"; + var metrics = context.measureText(this.text); + var textHeight = this.fontSize; + var textWidth = metrics.width; + var p = this.padding; + var x = 0; + var y = 0; + + switch (this.align) { + case "center": + x = textWidth / -2 - p; + break; + case "right": + x = -1 * textWidth - p; + break; + } + + switch (this.verticalAlign) { + case "middle": + y = textHeight / -2 - p; + break; + case "bottom": + y = -1 * textHeight - p; + break; + } + + // draw path + context.save(); + context.beginPath(); + context.rect(x, y, textWidth + p * 2, textHeight + p * 2); + context.closePath(); + this.fillStroke(); + context.restore(); + + var tx = p + x; + var ty = textHeight / 2 + p + y; + + // draw text + if (this.textFill !== undefined) { + context.fillStyle = this.textFill; + context.fillText(this.text, tx, ty); + } + if (this.textStroke !== undefined || this.textStrokeWidth !== undefined) { + // defaults + if (this.textStroke === undefined) { + this.textStroke = "black"; + } + else if (this.textStrokeWidth === undefined) { + this.textStrokeWidth = 2; + } + context.lineWidth = this.textStrokeWidth; + context.strokeStyle = this.textStroke; + context.strokeText(this.text, tx, ty); + } + }; + + // call super constructor + Kinetic.Shape.apply(this, [config]); +}; + +/* + * Text methods + */ +Kinetic.Text.prototype = { + setFontFamily: function(fontFamily){ + this.fontFamily = fontFamily; + }, + getFontFamily: function(){ + return this.fontFamily; + }, + setFontSize: function(fontSize){ + this.fontSize = fontSize; + }, + getFontSize: function(){ + return this.fontSize; + }, + setTextFill: function(textFill){ + this.textFill = textFill; + }, + getTextFill: function(){ + return this.textFill; + }, + setTextStroke: function(textStroke){ + this.textStroke = textStroke; + }, + getTextStroke: function(){ + return this.textStroke; + }, + setTextStrokeWidth: function(textStrokeWidth){ + this.textStrokeWidth = textStrokeWidth; + }, + getTextStrokeWidth: function(){ + return this.textStrokeWidth; + }, + setPadding: function(padding){ + this.padding = padding; + }, + getPadding: function(){ + return this.padding; + }, + setAlign: function(align){ + this.align = align; + }, + getAlign: function(){ + return this.align; + }, + setVerticalAlign: function(verticalAlign){ + this.verticalAlign = verticalAlign; + }, + getVerticalAlign: function(){ + return this.verticalAlign; + }, + setText: function(text){ + this.text = text; + }, + getText: function(){ + return this.text; + } +}; +// extend Shape +Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Shape); From ac802613a840a1b8ec6253f7bff242213a1f3ab5 Mon Sep 17 00:00:00 2001 From: Antoine Proulx Date: Mon, 5 Mar 2012 20:59:41 -0500 Subject: [PATCH 2/2] Create a build script to concatenate and (optionaly) minify the files in the lib directory. --- Gemfile | 4 +++ Gemfile.lock | 17 ++++++++++++ README.md | 8 ++++++ Rakefile | 37 +++++++++++++++++++++++++ dist/kinetic.js | 5 ++-- dist/kinetic.min.js | 31 +++++++++++++++++++++ lib/{GlobalObjet.js => GlobalObject.js} | 0 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Rakefile create mode 100644 dist/kinetic.min.js rename lib/{GlobalObjet.js => GlobalObject.js} (100%) diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..a57b6808 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source :rubygems + +gem 'rake' +gem 'uglifier' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..c0ac0a8f --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,17 @@ +GEM + remote: http://rubygems.org/ + specs: + execjs (1.3.0) + multi_json (~> 1.0) + multi_json (1.1.0) + rake (0.9.2.2) + uglifier (1.2.3) + execjs (>= 0.3.0) + multi_json (>= 1.0.2) + +PLATFORMS + ruby + +DEPENDENCIES + rake + uglifier diff --git a/README.md b/README.md index 98f79c25..2b9de8b7 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,11 @@ You can draw your own shapes or images using the existing canvas API, add event # Tutorials Check out the official [KineticJS Tutorials](http://www.html5canvastutorials.com/kineticjs/html5-canvas-events-tutorials-introduction-with-kineticjs/) hosted on [HTML5 Canvas Tutorials](http://www.html5canvastutorials.com/). + +# Building the library +To build the library, you need to have Ruby and Rubygems installed. After that, install the dependencies by running `bundle install`. + +To build a development version of the library, run `rake build:dev`. To build a minify version of the library, run `rake build:prod`. + +# Adding a new file in the lib directory +If you add a file in the lib directory, add into the array in the Rakefile. diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..abbc908c --- /dev/null +++ b/Rakefile @@ -0,0 +1,37 @@ +# This is the list of files to concatenate. The first file will appear at the top of the final file. All files are relative to the lib directory. +FILES = [ + "Kinetic.js", "GlobalObject.js", "Node.js", "Container.js", "Stage.js", + "Layer.js", "Group.js", "Shape.js", "Rect.js", "Circle.js", "Image.js", + "Polygon.js", "RegularPolygon.js", "Star.js", "Text.js" +] + +def concatenate + content = "" + FILES.each do |file| + content << IO.read(File.expand_path('lib/' + file)) << "\n" + end + + return content +end + +namespace :build do + desc "Concatenate all the files in /lib into /dist/kinetic.js." + task :dev do + puts ":: Building the file /dist/kinetic.js..." + File.open("dist/kinetic.js", "w") do |file| + file.puts concatenate() + end + puts " -> Done!" + end + + desc "Concatenate all the files in /lib into /dist/kinetic.min.js and minify it." + task :prod do + puts ":: Building the file /dist/kinetic.min.js..." + require 'uglifier' + File.open("dist/kinetic.min.js", "w") do |file| + file.puts Uglifier.compile(concatenate()) + end + puts ":: Minifying the file /dist/kinetic.min.js..." + puts " -> Done!" + end +end diff --git a/dist/kinetic.js b/dist/kinetic.js index f6d4cf4f..eaea0e6a 100644 --- a/dist/kinetic.js +++ b/dist/kinetic.js @@ -25,6 +25,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + /////////////////////////////////////////////////////////////////////// // Global Object /////////////////////////////////////////////////////////////////////// @@ -120,6 +121,7 @@ window.requestAnimFrame = (function(callback){ }; })(); + /////////////////////////////////////////////////////////////////////// // Node /////////////////////////////////////////////////////////////////////// @@ -1835,7 +1837,6 @@ Kinetic.Polygon.prototype = { // extend Shape Kinetic.GlobalObject.extend(Kinetic.Polygon, Kinetic.Shape); - /////////////////////////////////////////////////////////////////////// // RegularPolygon /////////////////////////////////////////////////////////////////////// @@ -1889,7 +1890,6 @@ Kinetic.RegularPolygon.prototype = { // extend Shape Kinetic.GlobalObject.extend(Kinetic.RegularPolygon, Kinetic.Shape); - /////////////////////////////////////////////////////////////////////// // Star /////////////////////////////////////////////////////////////////////// @@ -2098,3 +2098,4 @@ Kinetic.Text.prototype = { }; // extend Shape Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Shape); + diff --git a/dist/kinetic.min.js b/dist/kinetic.min.js new file mode 100644 index 00000000..366b0143 --- /dev/null +++ b/dist/kinetic.min.js @@ -0,0 +1,31 @@ +/** + * KineticJS JavaScript Library v3.8.2 + * http://www.kineticjs.com/ + * Copyright 2012, Eric Rowell + * Licensed under the MIT or GPL Version 2 licenses. + * Date: Mar 03 2012 + * + * Copyright (C) 2011 - 2012 by Eric Rowell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/////////////////////////////////////////////////////////////////////// +// Global Object +/////////////////////////////////////////////////////////////////////// +var Kinetic={};Kinetic.GlobalObject={stages:[],idCounter:0,isAnimating:!1,frame:{time:0,timeDiff:0,lastTime:0},drag:{moving:!1,node:undefined,offset:{x:0,y:0}},extend:function(a,b){for(var c in b.prototype)b.prototype.hasOwnProperty(c)&&(a.prototype[c]=b.prototype[c])},_isaCanvasAnimating:function(){for(var a=0;a1?g[1]:"";this.eventListeners[h]||(this.eventListeners[h]=[]),this.eventListeners[h].push({name:i,handler:b})}},off:function(a){var b=a.split(" ");for(var c=0;c1){var h=f[1];for(var i=0;i0&&(this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices())},moveToBottom:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices()},setZIndex:function(a){var b=this.index;this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices()},setAlpha:function(a){this.alpha=a},getAlpha:function(){return this.alpha},getAbsoluteAlpha:function(){var a=1,b=this;while(b.className!=="Stage")a*=b.alpha,b=b.parent;return a},_initDrag:function(){var a=Kinetic.GlobalObject,b=this;this.on("mousedown.initdrag touchstart.initdrag",function(c){var d=b.getStage(),e=d.getUserPosition();e&&(a.drag.node=b,a.drag.offset.x=e.x-b.x,a.drag.offset.y=e.y-b.y)})},_dragCleanup:function(){!this.drag.x&&!this.drag.y&&(this.off("mousedown.initdrag"),this.off("touchstart.initdrag"))},draggable:function(a){if(a){var b=!this.drag.x&&!this.drag.y;this.drag.x=!0,this.drag.y=!0,b&&this._initDrag()}else this.drag.x=!1,this.drag.y=!1,this._dragCleanup()},draggableX:function(a){if(a){var b=!this.drag.x&&!this.drag.y;this.drag.x=!0,b&&this._initDrag()}else this.drag.x=!1,this._dragCleanup()},draggableY:function(a){if(a){var b=!this.drag.x&&!this.drag.y;this.drag.y=!0,b&&this._initDrag()}else this.drag.y=!1,this._dragCleanup()},isDragging:function(){var a=Kinetic.GlobalObject;return a.drag.node!==undefined&&a.drag.node.id===this.id&&a.drag.moving},_handleEvents:function(a,b){function c(d){var e=d.eventListeners;if(e[a]){var f=e[a];for(var g=0;g0)this.remove(this.children[0])}},Kinetic.Stage=function(a,b,c){this.className="Stage",this.container=typeof a=="string"?document.getElementById(a):a,this.width=b,this.height=c,this.scale={x:1,y:1},this.dblClickWindow=400,this.targetShape=undefined,this.clickStart=!1,this.mousePos=undefined,this.mouseDown=!1,this.mouseUp=!1,this.touchPos=undefined,this.touchStart=!1,this.touchEnd=!1,this.bufferLayer=new Kinetic.Layer,this.backstageLayer=new Kinetic.Layer,this.bufferLayer.parent=this,this.backstageLayer.parent=this;var d=this.backstageLayer;this._stripLayer(d),this.bufferLayer.getCanvas().style.display="none",this.backstageLayer.getCanvas().style.display="none",this.bufferLayer.canvas.width=this.width,this.bufferLayer.canvas.height=this.height,this.container.appendChild(this.bufferLayer.canvas),this.backstageLayer.canvas.width=this.width,this.backstageLayer.canvas.height=this.height,this.container.appendChild(this.backstageLayer.canvas),this._listen(),this._prepareDrag();var e=Kinetic.GlobalObject.stages;e.push(this),this.id=Kinetic.GlobalObject.idCounter++,this.isAnimating=!1,this.onFrameFunc=undefined,Kinetic.Container.apply(this,[])},Kinetic.Stage.prototype={onFrame:function(a){this.onFrameFunc=a},start:function(){this.isAnimating=!0,Kinetic.GlobalObject._handleAnimation()},stop:function(){this.isAnimating=!1,Kinetic.GlobalObject._handleAnimation()},draw:function(){this._drawChildren()},_stripLayer:function(a){a.context.stroke=function(){},a.context.fill=function(){},a.context.fillRect=function(b,c,d,e){a.context.rect(b,c,d,e)},a.context.strokeRect=function(b,c,d,e){a.context.rect(b,c,d,e)},a.context.drawImage=function(){},a.context.fillText=function(){},a.context.strokeText=function(){}},_endDrag:function(a){var b=Kinetic.GlobalObject;b.drag.node&&b.drag.moving&&(b.drag.moving=!1,b.drag.node._handleEvents("ondragend",a)),b.drag.node=undefined},_prepareDrag:function(){var a=this;this.on("mousemove touchmove",function(b){var c=Kinetic.GlobalObject;if(c.drag.node){var d=a.getUserPosition();c.drag.node.drag.x&&(c.drag.node.x=d.x-c.drag.offset.x),c.drag.node.drag.y&&(c.drag.node.y=d.y-c.drag.offset.y),c.drag.node.getLayer().draw(),c.drag.moving||(c.drag.moving=!0,c.drag.node._handleEvents("ondragstart",b)),c.drag.node._handleEvents("ondragmove",b)}},!1),this.on("mouseup touchend mouseout",function(b){a._endDrag(b)})},setSize:function(a,b){var c=this.children;for(var d=0;d=0;c--){var d=b[c];if(d.className==="Shape"){var e=f(d);if(e)return!0}else g(d)}return!1}a||(a=window.event),this._setMousePosition(a),this._setTouchPosition(a);var b=this.backstageLayer,c=b.getContext(),d=this;b.clear();var e=!1,h=Kinetic.GlobalObject;if(h.drag.node===undefined)for(var i=this.children.length-1;i>=0;i--){var j=this.children[i];j.visible&&i>=0&&j.isListening&&g(j)&&(i=-1)}},_listen:function(){var a=this;this.container.addEventListener("mousedown",function(b){a.mouseDown=!0,a._handleEvent(b)},!1),this.container.addEventListener("mousemove",function(b){a.mouseUp=!1,a.mouseDown=!1,a._handleEvent(b)},!1),this.container.addEventListener("mouseup",function(b){a.mouseUp=!0,a.mouseDown=!1,a._handleEvent(b),a.clickStart=!1},!1),this.container.addEventListener("mouseover",function(b){a._handleEvent(b)},!1),this.container.addEventListener("mouseout",function(b){a.mousePos=undefined},!1),this.container.addEventListener("touchstart",function(b){b.preventDefault(),a.touchStart=!0,a._handleEvent(b)},!1),this.container.addEventListener("touchmove",function(b){b.preventDefault(),a._handleEvent(b)},!1),this.container.addEventListener("touchend",function(b){b.preventDefault(),a.touchEnd=!0,a._handleEvent(b)},!1)},getMousePosition:function(a){return this.mousePos},getTouchPosition:function(a){return this.touchPos},getUserPosition:function(a){return this.getTouchPosition()||this.getMousePosition()},_setMousePosition:function(a){var b=a.clientX-this._getContainerPosition().left+window.pageXOffset,c=a.clientY-this._getContainerPosition().top+window.pageYOffset;this.mousePos={x:b,y:c}},_setTouchPosition:function(a){if(a.touches!==undefined&&a.touches.length===1){var b=a.touches[0],c=b.clientX-this._getContainerPosition().left+window.pageXOffset,d=b.clientY-this._getContainerPosition().top+window.pageYOffset;this.touchPos={x:c,y:d}}},_getContainerPosition:function(){var a=this.container,b=0,c=0;while(a&&a.tagName!=="BODY")b+=a.offsetTop,c+=a.offsetLeft,a=a.offsetParent;return{top:b,left:c}},getContainer:function(){return this.container},getStage:function(){return this},getTargetShape:function(){return this.targetShape}},Kinetic.GlobalObject.extend(Kinetic.Stage,Kinetic.Container),Kinetic.Layer=function(a){this.className="Layer",this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),this.canvas.style.position="absolute",Kinetic.Container.apply(this,[]),Kinetic.Node.apply(this,[a])},Kinetic.Layer.prototype={draw:function(){this._draw()},_draw:function(){this.clear(),this.visible&&this._drawChildren()},clear:function(){var a=this.getContext(),b=this.getCanvas();a.clearRect(0,0,b.width,b.height)},getCanvas:function(){return this.canvas},getContext:function(){return this.context},add:function(a){this._add(a)},remove:function(a){this._remove(a)}},Kinetic.GlobalObject.extend(Kinetic.Layer,Kinetic.Container),Kinetic.GlobalObject.extend(Kinetic.Layer,Kinetic.Node),Kinetic.Group=function(a){this.className="Group",Kinetic.Container.apply(this,[]),Kinetic.Node.apply(this,[a])},Kinetic.Group.prototype={_draw:function(){this.visible&&this._drawChildren()},add:function(a){this._add(a)},remove:function(a){this._remove(a)}},Kinetic.GlobalObject.extend(Kinetic.Group,Kinetic.Container),Kinetic.GlobalObject.extend(Kinetic.Group,Kinetic.Node),Kinetic.Shape=function(a){this.className="Shape";if(a.stroke!==undefined||a.strokeWidth!==undefined)a.stroke===undefined?a.stroke="black":a.strokeWidth===undefined&&(a.strokeWidth=2);this.drawFunc=a.drawFunc,Kinetic.Node.apply(this,[a])},Kinetic.Shape.prototype={getContext:function(){return this.tempLayer.getContext()},getCanvas:function(){return this.tempLayer.getCanvas()},_draw:function(a){if(this.visible){var b=a.getStage(),c=a.getContext(),d=[];d.unshift(this);var e=this.parent;while(e.className!=="Stage")d.unshift(e),e=e.parent;for(var f=0;f