diff --git a/.travis.yml b/.travis.yml index 587bd3e..38b4d1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,2 @@ language: node_js +node_js: 4 diff --git a/examples/mouseEvents.js b/examples/mouseEvents.js index 5b1dded..e516859 100755 --- a/examples/mouseEvents.js +++ b/examples/mouseEvents.js @@ -3,6 +3,7 @@ var asterisk; var ghost; +var draggedSprite; function setup() { createCanvas(800, 400); @@ -34,11 +35,17 @@ function setup() { asterisk.onMousePressed = function() { this.changeAnimation('transform'); this.animation.goToFrame(this.animation.getLastFrame()); + if (draggedSprite == null) { + draggedSprite = this; + } }; asterisk.onMouseReleased = function() { this.changeAnimation('transform'); this.animation.goToFrame(0); + if (draggedSprite == this) { + draggedSprite = null; + } }; } @@ -46,6 +53,11 @@ function setup() { function draw() { background(255, 255, 255); + if (draggedSprite != null) { + draggedSprite.position.x = mouseX; + draggedSprite.position.y = mouseY; + } + //if a sprite is mouseActive true I can check if the mouse is over its collider //and if the button is pressed if(ghost.mouseIsOver) diff --git a/lib/p5.play.js b/lib/p5.play.js index 5b0631c..7f91a37 100644 --- a/lib/p5.play.js +++ b/lib/p5.play.js @@ -128,7 +128,9 @@ var round = p5.prototype.round; * @type {Group} */ -defineLazyP5Property('allSprites', function() { return new Group(); }); +defineLazyP5Property('allSprites', function() { + return new Group(); +}); p5.prototype.spriteUpdate = true; @@ -1011,6 +1013,48 @@ function Sprite(pInst, _x, _y, _w, _h) { */ this.mouseIsPressed = false; + /* + * Width of the sprite's current image. + * If no images or animations are set it's the width of the + * placeholder rectangle. + * Used internally to make calculations and draw the sprite. + * + * @private + * @property _internalWidth + * @type {Number} + * @default 100 + */ + this._internalWidth = _w; + + /* + * Height of the sprite's current image. + * If no images or animations are set it's the height of the + * placeholder rectangle. + * Used internally to make calculations and draw the sprite. + * + * @private + * @property _internalHeight + * @type {Number} + * @default 100 + */ + this._internalHeight = _h; + + /* + * _internalWidth and _internalHeight are used for all p5.play + * calculations, but width and height can be extended. For example, + * you may want users to always get and set a scaled width: + Object.defineProperty(this, 'width', { + enumerable: true, + configurable: true, + get: function() { + return this._internalWidth * this.scale; + }, + set: function(value) { + this._internalWidth = value / this.scale; + } + }); + */ + /** * Width of the sprite's current image. * If no images or animations are set it's the width of the @@ -1020,6 +1064,17 @@ function Sprite(pInst, _x, _y, _w, _h) { * @type {Number} * @default 100 */ + Object.defineProperty(this, 'width', { + enumerable: true, + configurable: true, + get: function() { + return this._internalWidth; + }, + set: function(value) { + this._internalWidth = value; + } + }); + if(_w === undefined) this.width = 100; else @@ -1034,6 +1089,17 @@ function Sprite(pInst, _x, _y, _w, _h) { * @type {Number} * @default 100 */ + Object.defineProperty(this, 'height', { + enumerable: true, + configurable: true, + get: function() { + return this._internalHeight; + }, + set: function(value) { + this._internalHeight = value; + } + }); + if(_h === undefined) this.height = 100; else @@ -1048,7 +1114,7 @@ function Sprite(pInst, _x, _y, _w, _h) { * @type {Number} * @default 100 */ - this.originalWidth = this.width; + this.originalWidth = this._internalWidth; /** * Unscaled height of the sprite @@ -1059,7 +1125,7 @@ function Sprite(pInst, _x, _y, _w, _h) { * @type {Number} * @default 100 */ - this.originalHeight = this.height; + this.originalHeight = this._internalHeight; /** * True if the sprite has been removed. @@ -1223,11 +1289,11 @@ function Sprite(pInst, _x, _y, _w, _h) { } else if(this.colliderType === 'image') { - this.collider.extents.x = this.width * abs(cos(t)) + - this.height * abs(sin(t)); + this.collider.extents.x = this._internalWidth * abs(cos(t)) + + this._internalHeight * abs(sin(t)); - this.collider.extents.y = this.width * abs(sin(t)) + - this.height * abs(cos(t)); + this.collider.extents.y = this._internalWidth * abs(sin(t)) + + this._internalHeight * abs(cos(t)); } } @@ -1289,8 +1355,8 @@ function Sprite(pInst, _x, _y, _w, _h) { if(animations[currentAnimation] && (animations[currentAnimation].getWidth() !== 1 && animations[currentAnimation].getHeight() !== 1)) { this.collider = this.getBoundingBox(); - this.width = animations[currentAnimation].getWidth()*abs(this._getScaleX()); - this.height = animations[currentAnimation].getHeight()*abs(this._getScaleY()); + this._internalWidth = animations[currentAnimation].getWidth()*abs(this.scale); + this._internalHeight = animations[currentAnimation].getHeight()*abs(this.scale); //quadTree.insert(this); this.colliderType = 'image'; //print("IMAGE COLLIDER ADDED"); @@ -1302,7 +1368,7 @@ function Sprite(pInst, _x, _y, _w, _h) { } else //get the with and height defined at the creation { - this.collider = new AABB(pInst, this.position, createVector(this.width, this.height)); + this.collider = new AABB(pInst, this.position, createVector(this._internalWidth, this._internalHeight)); //quadTree.insert(this); this.colliderType = 'default'; } @@ -1373,7 +1439,7 @@ function Sprite(pInst, _x, _y, _w, _h) { else print('Warning: onMousePressed should be a function'); - if(mouseWasPressed && !this.mouseIsPressed && this.onMouseReleased !== undefined) + if(mouseWasPressed && !pInst.mouseIsPressed && !this.mouseIsPressed && this.onMouseReleased !== undefined) if(typeof(this.onMouseReleased) === 'function') this.onMouseReleased.call(this, this); else @@ -1584,7 +1650,7 @@ function Sprite(pInst, _x, _y, _w, _h) { { noStroke(); fill(this.shapeColor); - rect(0, 0, this.width, this.height); + rect(0, 0, this._internalWidth, this._internalHeight); } }; diff --git a/package.json b/package.json index 153a938..732efd0 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,14 @@ "description": "A p5.js library for the creation of games and playthings.", "dependencies": {}, "devDependencies": { - "eslint": ">=2.5.3 <2.10.0", + "eslint": "^3.1.0", "http-server": "^0.9.0", "mocha-phantomjs": "^4.0.1", "yuidocjs": "^0.10.0" }, + "engines": { + "node" : ">=4.0.0" + }, "scripts": { "docs": "yuidoc .", "lint": "eslint lib/** test/unit/** examples/*.js", diff --git a/test/unit/sprite.js b/test/unit/sprite.js index 7f03afd..89f0a9c 100644 --- a/test/unit/sprite.js +++ b/test/unit/sprite.js @@ -127,4 +127,167 @@ describe('Sprite', function() { expect(sprite.height).to.equal(30); }); }); + + describe('mouse events', function() { + var sprite; + + beforeEach(function() { + // Create a sprite with centered at 50,50 with size 100,100. + // Its default collider picks up anything from 1,1 to 99,99. + sprite = pInst.createSprite(50, 50, 100, 100); + sprite.onMouseOver = sinon.spy(); + sprite.onMouseOut = sinon.spy(); + sprite.onMousePressed = sinon.spy(); + sprite.onMouseReleased = sinon.spy(); + }); + + function moveMouseTo(x, y) { + pInst.mouseX = x; + pInst.mouseY = y; + sprite.update(); + } + + function moveMouseOver() { + moveMouseTo(1, 1); + } + + function moveMouseOut() { + moveMouseTo(0, 0); + } + + function pressMouse() { + pInst.mouseIsPressed = true; + sprite.update(); + } + + function releaseMouse() { + pInst.mouseIsPressed = false; + sprite.update(); + } + + it('mouseIsOver property represents whether mouse is over collider', function() { + moveMouseTo(0, 0); + expect(sprite.mouseIsOver).to.be.false; + moveMouseTo(1, 1); + expect(sprite.mouseIsOver).to.be.true; + moveMouseTo(99, 99); + expect(sprite.mouseIsOver).to.be.true; + moveMouseTo(100, 100); + expect(sprite.mouseIsOver).to.be.false; + }); + + describe('onMouseOver callback', function() { + it('calls onMouseOver when the mouse enters the sprite collider', function() { + moveMouseOut(); + expect(sprite.onMouseOver.called).to.be.false; + moveMouseOver(); + expect(sprite.onMouseOver.called).to.be.true; + }); + + it('does not call onMouseOver when the mouse moves within the sprite collider', function() { + moveMouseTo(0, 0); + expect(sprite.onMouseOver.callCount).to.equal(0); + moveMouseTo(1, 1); + expect(sprite.onMouseOver.callCount).to.equal(1); + moveMouseTo(2, 2); + expect(sprite.onMouseOver.callCount).to.equal(1); + }); + + it('calls onMouseOver again when the mouse leaves and returns', function() { + moveMouseOut(); + expect(sprite.onMouseOver.callCount).to.equal(0); + moveMouseOver(); + expect(sprite.onMouseOver.callCount).to.equal(1); + moveMouseOut(); + expect(sprite.onMouseOver.callCount).to.equal(1); + moveMouseOver(); + expect(sprite.onMouseOver.callCount).to.equal(2); + }); + }); + + describe('onMouseOut callback', function() { + it('calls onMouseOut when the mouse leaves the sprite collider', function() { + moveMouseOver(); + expect(sprite.onMouseOut.called).to.be.false; + moveMouseOut(); + expect(sprite.onMouseOut.called).to.be.true; + }); + + it('does not call onMouseOut when the mouse moves outside the sprite collider', function() { + moveMouseTo(0, 0); + expect(sprite.onMouseOut.called).to.be.false; + moveMouseTo(0, 1); + expect(sprite.onMouseOut.called).to.be.false; + }); + + it('calls onMouseOut again when the mouse returns and leaves', function() { + moveMouseOver(); + expect(sprite.onMouseOut.callCount).to.equal(0); + moveMouseOut(); + expect(sprite.onMouseOut.callCount).to.equal(1); + moveMouseOver(); + expect(sprite.onMouseOut.callCount).to.equal(1); + moveMouseOut(); + expect(sprite.onMouseOut.callCount).to.equal(2); + }); + }); + + describe('onMousePressed callback', function() { + it('does not call onMousePressed if the mouse was not over the sprite', function() { + expect(sprite.mouseIsOver).to.be.false; + pressMouse(); + expect(sprite.onMousePressed.called).to.be.false; + }); + + it('calls onMousePressed if the mouse was pressed over the sprite', function() { + moveMouseOver(); + pressMouse(); + expect(sprite.onMousePressed.called).to.be.true; + }); + + it('calls onMousePressed if the mouse was pressed outside the sprite then dragged over it', function() { + pressMouse(); + moveMouseOver(); + expect(sprite.onMousePressed.called).to.be.true; + }); + }); + + describe('onMouseReleased callback', function() { + it('does not call onMouseReleased if the mouse was never pressed over the sprite', function() { + expect(sprite.mouseIsOver).to.be.false; + pressMouse(); + releaseMouse(); + expect(sprite.onMouseReleased.called).to.be.false; + }); + + it('calls onMouseReleased if the mouse was pressed and released over the sprite', function() { + moveMouseOver(); + pressMouse(); + releaseMouse(); + expect(sprite.onMouseReleased.called).to.be.true; + }); + + it('calls onMouseReleased if the mouse was pressed, moved over the sprite, and then released', function() { + pressMouse(); + moveMouseOver(); + releaseMouse(); + expect(sprite.onMouseReleased.called).to.be.true; + }); + + it('does not call onMouseReleased on mouse-out if mouse is still down', function() { + pressMouse(); + moveMouseOver(); + moveMouseOut(); + expect(sprite.onMouseReleased.called).to.be.false; + }); + + it('does not call onMouseReleased on release if mouse has left sprite', function() { + moveMouseOver(); + pressMouse(); + moveMouseOut(); + releaseMouse(); + expect(sprite.onMouseReleased.called).to.be.false; + }); + }); + }); });