diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 32dc791..29a8a5e 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -1014,24 +1014,45 @@ let hit = false; let interactiveParent = interactive; - // if the displayobject has a hitArea, then it does not need to hitTest children. + // Flag here can set to false if the event is outside the parents hitArea or mask + let hitTestChildren = true; + + // If there is a hitArea, no need to test against anything else if the pointer is not within the hitArea + // There is also no longer a need to hitTest children. if (displayObject.hitArea) { + if (hitTest) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + if (!displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hitTest = false; + hitTestChildren = false; + } + else + { + hit = true; + } + } interactiveParent = false; } - // it has a mask! Then lets hit test that before continuing - else if (hitTest && displayObject._mask) + // If there is a mask, no need to test against anything else if the pointer is not within the mask + else if (displayObject._mask) { - if (!displayObject._mask.containsPoint(point)) + if (hitTest) { - hitTest = false; + if (!displayObject._mask.containsPoint(point)) + { + hitTest = false; + hitTestChildren = false; + } } } // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow PixiJS to completely ignore and bypass checking the displayObjects children. - if (displayObject.interactiveChildren && displayObject.children) + if (hitTestChildren && displayObject.interactiveChildren && displayObject.children) { const children = displayObject.children; @@ -1081,15 +1102,8 @@ // looking for an interactive child, just in case we hit one if (hitTest && !interactionEvent.target) { - if (displayObject.hitArea) - { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) - { - hit = true; - } - } - else if (displayObject.containsPoint) + // already tested against hitArea if it is defined + if (!displayObject.hitArea && displayObject.containsPoint) { if (displayObject.containsPoint(point)) { diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 32dc791..29a8a5e 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -1014,24 +1014,45 @@ let hit = false; let interactiveParent = interactive; - // if the displayobject has a hitArea, then it does not need to hitTest children. + // Flag here can set to false if the event is outside the parents hitArea or mask + let hitTestChildren = true; + + // If there is a hitArea, no need to test against anything else if the pointer is not within the hitArea + // There is also no longer a need to hitTest children. if (displayObject.hitArea) { + if (hitTest) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + if (!displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hitTest = false; + hitTestChildren = false; + } + else + { + hit = true; + } + } interactiveParent = false; } - // it has a mask! Then lets hit test that before continuing - else if (hitTest && displayObject._mask) + // If there is a mask, no need to test against anything else if the pointer is not within the mask + else if (displayObject._mask) { - if (!displayObject._mask.containsPoint(point)) + if (hitTest) { - hitTest = false; + if (!displayObject._mask.containsPoint(point)) + { + hitTest = false; + hitTestChildren = false; + } } } // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow PixiJS to completely ignore and bypass checking the displayObjects children. - if (displayObject.interactiveChildren && displayObject.children) + if (hitTestChildren && displayObject.interactiveChildren && displayObject.children) { const children = displayObject.children; @@ -1081,15 +1102,8 @@ // looking for an interactive child, just in case we hit one if (hitTest && !interactionEvent.target) { - if (displayObject.hitArea) - { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) - { - hit = true; - } - } - else if (displayObject.containsPoint) + // already tested against hitArea if it is defined + if (!displayObject.hitArea && displayObject.containsPoint) { if (displayObject.containsPoint(point)) { diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 505df4c..0300129 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -1,6 +1,5 @@ 'use strict'; -const MockPointer = require('../interaction/MockPointer'); const withGL = require('../withGL'); describe('PIXI.Graphics', function () @@ -244,115 +243,8 @@ }); }); - describe('mask', function () + describe('fastRect', function () { - it('should trigger interaction callback when no mask present', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.beginFill(); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - it('should trigger interaction callback when mask uses beginFill', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.beginFill(); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - - it('should not trigger interaction callback when mask doesn\'t use beginFill', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.not.been.called; - }); - - it('should trigger interaction callback when mask doesn\'t use beginFill but hitArea is defined', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.hitArea = new PIXI.Rectangle(0, 0, 50, 50); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - - it('should trigger interaction callback when mask is a sprite', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.drawRect(0, 0, 50, 50); - graphics.mask = new PIXI.Sprite(mask.generateCanvasTexture()); - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - it('should calculate tint, alpha and blendMode of fastRect correctly', withGL(function () { const renderer = new PIXI.WebGLRenderer(200, 200, {}); diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 32dc791..29a8a5e 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -1014,24 +1014,45 @@ let hit = false; let interactiveParent = interactive; - // if the displayobject has a hitArea, then it does not need to hitTest children. + // Flag here can set to false if the event is outside the parents hitArea or mask + let hitTestChildren = true; + + // If there is a hitArea, no need to test against anything else if the pointer is not within the hitArea + // There is also no longer a need to hitTest children. if (displayObject.hitArea) { + if (hitTest) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + if (!displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hitTest = false; + hitTestChildren = false; + } + else + { + hit = true; + } + } interactiveParent = false; } - // it has a mask! Then lets hit test that before continuing - else if (hitTest && displayObject._mask) + // If there is a mask, no need to test against anything else if the pointer is not within the mask + else if (displayObject._mask) { - if (!displayObject._mask.containsPoint(point)) + if (hitTest) { - hitTest = false; + if (!displayObject._mask.containsPoint(point)) + { + hitTest = false; + hitTestChildren = false; + } } } // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow PixiJS to completely ignore and bypass checking the displayObjects children. - if (displayObject.interactiveChildren && displayObject.children) + if (hitTestChildren && displayObject.interactiveChildren && displayObject.children) { const children = displayObject.children; @@ -1081,15 +1102,8 @@ // looking for an interactive child, just in case we hit one if (hitTest && !interactionEvent.target) { - if (displayObject.hitArea) - { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) - { - hit = true; - } - } - else if (displayObject.containsPoint) + // already tested against hitArea if it is defined + if (!displayObject.hitArea && displayObject.containsPoint) { if (displayObject.containsPoint(point)) { diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 505df4c..0300129 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -1,6 +1,5 @@ 'use strict'; -const MockPointer = require('../interaction/MockPointer'); const withGL = require('../withGL'); describe('PIXI.Graphics', function () @@ -244,115 +243,8 @@ }); }); - describe('mask', function () + describe('fastRect', function () { - it('should trigger interaction callback when no mask present', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.beginFill(); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - it('should trigger interaction callback when mask uses beginFill', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.beginFill(); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - - it('should not trigger interaction callback when mask doesn\'t use beginFill', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.not.been.called; - }); - - it('should trigger interaction callback when mask doesn\'t use beginFill but hitArea is defined', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.hitArea = new PIXI.Rectangle(0, 0, 50, 50); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.drawRect(0, 0, 50, 50); - graphics.mask = mask; - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - - it('should trigger interaction callback when mask is a sprite', function () - { - const stage = new PIXI.Container(); - const pointer = new MockPointer(stage); - const graphics = new PIXI.Graphics(); - const mask = new PIXI.Graphics(); - const spy = sinon.spy(); - - graphics.interactive = true; - graphics.beginFill(0xFF0000); - graphics.drawRect(0, 0, 50, 50); - graphics.on('click', spy); - stage.addChild(graphics); - mask.drawRect(0, 0, 50, 50); - graphics.mask = new PIXI.Sprite(mask.generateCanvasTexture()); - - pointer.click(10, 10); - - expect(spy).to.have.been.calledOnce; - }); - it('should calculate tint, alpha and blendMode of fastRect correctly', withGL(function () { const renderer = new PIXI.WebGLRenderer(200, 200, {}); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 136640e..4676252 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -1087,6 +1087,248 @@ }); }); + describe('masks', function () + { + it('should trigger interaction callback when no mask present', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + mask.beginFill(); + mask.drawRect(0, 0, 50, 50); + graphics.mask = mask; + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + + it('should trigger interaction callback when mask uses beginFill', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + mask.beginFill(); + mask.drawRect(0, 0, 50, 50); + graphics.mask = mask; + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + + it('should trigger interaction callback on child when inside of parents mask', function () + { + const stage = new PIXI.Container(); + const parent = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(parent); + parent.addChild(graphics); + mask.beginFill(); + mask.drawRect(0, 0, 25, 25); + parent.mask = mask; + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + + it('should not trigger interaction callback on child when outside of parents mask', function () + { + const stage = new PIXI.Container(); + const parent = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(parent); + parent.addChild(graphics); + mask.beginFill(); + mask.drawRect(0, 0, 25, 25); + parent.mask = mask; + + pointer.click(30, 30); + + expect(spy).to.have.not.been.calledOnce; + }); + + it('should not trigger interaction callback when mask doesn\'t use beginFill', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + mask.drawRect(0, 0, 50, 50); + graphics.mask = mask; + + pointer.click(10, 10); + + expect(spy).to.have.not.been.called; + }); + + it('should trigger interaction callback when mask doesn\'t use beginFill but hitArea is defined', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.hitArea = new PIXI.Rectangle(0, 0, 50, 50); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + mask.drawRect(0, 0, 50, 50); + graphics.mask = mask; + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + + it('should trigger interaction callback when mask is a sprite', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const mask = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + mask.drawRect(0, 0, 50, 50); + graphics.mask = new PIXI.Sprite(mask.generateCanvasTexture()); + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + }); + + describe('hitArea', function () + { + it('should trigger interaction callback when within hitArea', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + graphics.hitArea = new PIXI.Rectangle(0, 0, 25, 25); + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + + it('should not trigger interaction callback when not within hitArea', function () + { + const stage = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(graphics); + graphics.hitArea = new PIXI.Rectangle(0, 0, 25, 25); + + pointer.click(30, 30); + + expect(spy).to.have.not.been.calledOnce; + }); + + it('should trigger interaction callback on child when inside of parents hitArea', function () + { + const stage = new PIXI.Container(); + const parent = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(parent); + parent.addChild(graphics); + parent.hitArea = new PIXI.Rectangle(0, 0, 25, 25); + + pointer.click(10, 10); + + expect(spy).to.have.been.calledOnce; + }); + + it('should not trigger interaction callback on child when outside of parents hitArea', function () + { + const stage = new PIXI.Container(); + const parent = new PIXI.Container(); + const pointer = new MockPointer(stage); + const graphics = new PIXI.Graphics(); + const spy = sinon.spy(); + + graphics.interactive = true; + graphics.beginFill(0xFF0000); + graphics.drawRect(0, 0, 50, 50); + graphics.on('click', spy); + stage.addChild(parent); + parent.addChild(graphics); + parent.hitArea = new PIXI.Rectangle(0, 0, 25, 25); + + pointer.click(30, 30); + + expect(spy).to.have.not.been.calledOnce; + }); + }); + describe('cursor changes', function () { it('cursor should be the cursor of interactive item', function ()