diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/test/core/Application.js b/test/core/Application.js index aa726b0..00eb286 100644 --- a/test/core/Application.js +++ b/test/core/Application.js @@ -17,4 +17,21 @@ done(); }); }); + + it('should remove canvas when destroyed', function (done) + { + const app = new PIXI.Application(); + const view = app.view; + + expect(view).to.be.instanceof(HTMLCanvasElement); + document.body.appendChild(view); + + app.ticker.addOnce(() => + { + expect(document.body.contains(view)).to.be.true; + app.destroy(true); + expect(document.body.contains(view)).to.be.false; + done(); + }); + }); }); diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/test/core/Application.js b/test/core/Application.js index aa726b0..00eb286 100644 --- a/test/core/Application.js +++ b/test/core/Application.js @@ -17,4 +17,21 @@ done(); }); }); + + it('should remove canvas when destroyed', function (done) + { + const app = new PIXI.Application(); + const view = app.view; + + expect(view).to.be.instanceof(HTMLCanvasElement); + document.body.appendChild(view); + + app.ticker.addOnce(() => + { + expect(document.body.contains(view)).to.be.true; + app.destroy(true); + expect(document.body.contains(view)).to.be.false; + done(); + }); + }); }); diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 11e47a0..505df4c 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -140,6 +140,28 @@ expect(graphics.containsPoint(point)).to.be.false; }); + + it('should return false with hole', function () + { + const point1 = new PIXI.Point(1, 1); + const point2 = new PIXI.Point(5, 5); + const graphics = new PIXI.Graphics(); + + graphics.beginFill(0) + .moveTo(0, 0) + .lineTo(10, 0) + .lineTo(10, 10) + .lineTo(0, 10) + // draw hole + .moveTo(2, 2) + .lineTo(8, 2) + .lineTo(8, 8) + .lineTo(2, 8) + .addHole(); + + expect(graphics.containsPoint(point1)).to.be.true; + expect(graphics.containsPoint(point2)).to.be.false; + }); }); describe('arc', function () diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/test/core/Application.js b/test/core/Application.js index aa726b0..00eb286 100644 --- a/test/core/Application.js +++ b/test/core/Application.js @@ -17,4 +17,21 @@ done(); }); }); + + it('should remove canvas when destroyed', function (done) + { + const app = new PIXI.Application(); + const view = app.view; + + expect(view).to.be.instanceof(HTMLCanvasElement); + document.body.appendChild(view); + + app.ticker.addOnce(() => + { + expect(document.body.contains(view)).to.be.true; + app.destroy(true); + expect(document.body.contains(view)).to.be.false; + done(); + }); + }); }); diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 11e47a0..505df4c 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -140,6 +140,28 @@ expect(graphics.containsPoint(point)).to.be.false; }); + + it('should return false with hole', function () + { + const point1 = new PIXI.Point(1, 1); + const point2 = new PIXI.Point(5, 5); + const graphics = new PIXI.Graphics(); + + graphics.beginFill(0) + .moveTo(0, 0) + .lineTo(10, 0) + .lineTo(10, 10) + .lineTo(0, 10) + // draw hole + .moveTo(2, 2) + .lineTo(8, 2) + .lineTo(8, 8) + .lineTo(2, 8) + .addHole(); + + expect(graphics.containsPoint(point1)).to.be.true; + expect(graphics.containsPoint(point2)).to.be.false; + }); }); describe('arc', function () diff --git a/test/core/Texture.js b/test/core/Texture.js index 6e1109c..0a7919d 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -70,26 +70,6 @@ expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); }); - it('should be added to the texture cache correctly using legacy addTextureToCache, ' - + 'and should remove also remove the base texture from its cache with removeTextureFromCache', function () - { - cleanCache(); - - const texture = new PIXI.Texture(new PIXI.BaseTexture()); - - PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); - PIXI.Texture.addTextureToCache(texture, NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); - expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); - PIXI.Texture.removeTextureFromCache(NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); - expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); - }); - it('should remove Texture from entire cache using removeFromCache (by Texture instance)', function () { cleanCache(); diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/test/core/Application.js b/test/core/Application.js index aa726b0..00eb286 100644 --- a/test/core/Application.js +++ b/test/core/Application.js @@ -17,4 +17,21 @@ done(); }); }); + + it('should remove canvas when destroyed', function (done) + { + const app = new PIXI.Application(); + const view = app.view; + + expect(view).to.be.instanceof(HTMLCanvasElement); + document.body.appendChild(view); + + app.ticker.addOnce(() => + { + expect(document.body.contains(view)).to.be.true; + app.destroy(true); + expect(document.body.contains(view)).to.be.false; + done(); + }); + }); }); diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 11e47a0..505df4c 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -140,6 +140,28 @@ expect(graphics.containsPoint(point)).to.be.false; }); + + it('should return false with hole', function () + { + const point1 = new PIXI.Point(1, 1); + const point2 = new PIXI.Point(5, 5); + const graphics = new PIXI.Graphics(); + + graphics.beginFill(0) + .moveTo(0, 0) + .lineTo(10, 0) + .lineTo(10, 10) + .lineTo(0, 10) + // draw hole + .moveTo(2, 2) + .lineTo(8, 2) + .lineTo(8, 8) + .lineTo(2, 8) + .addHole(); + + expect(graphics.containsPoint(point1)).to.be.true; + expect(graphics.containsPoint(point2)).to.be.false; + }); }); describe('arc', function () diff --git a/test/core/Texture.js b/test/core/Texture.js index 6e1109c..0a7919d 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -70,26 +70,6 @@ expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); }); - it('should be added to the texture cache correctly using legacy addTextureToCache, ' - + 'and should remove also remove the base texture from its cache with removeTextureFromCache', function () - { - cleanCache(); - - const texture = new PIXI.Texture(new PIXI.BaseTexture()); - - PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); - PIXI.Texture.addTextureToCache(texture, NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); - expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); - PIXI.Texture.removeTextureFromCache(NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); - expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); - }); - it('should remove Texture from entire cache using removeFromCache (by Texture instance)', function () { cleanCache(); diff --git a/test/core/util.js b/test/core/util.js index 239aaeb..68c7e6d 100755 --- a/test/core/util.js +++ b/test/core/util.js @@ -280,39 +280,9 @@ describe('.removeItems', function () { - var arr; - - beforeEach(() => + it('should exist', function () { - arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - }); - - it('should return if the start index is greater than or equal to the length of the array', function () - { - PIXI.utils.removeItems(arr, arr.length + 1, 5); - expect(arr.length).to.be.equal(10); - }); - - it('should return if the remove count is 0', function () - { - PIXI.utils.removeItems(arr, 2, 0); - expect(arr.length).to.be.equal(10); - }); - - it('should remove the number of elements specified from the array, starting from the start index', function () - { - const res = [1, 2, 3, 8, 9, 10]; - - PIXI.utils.removeItems(arr, 3, 4); - expect(arr).to.be.deep.equal(res); - }); - - it('should remove other elements if delete count is > than the number of elements after start index', function () - { - const res = [1, 2, 3, 4, 5, 6, 7]; - - PIXI.utils.removeItems(arr, 7, 10); - expect(arr).to.be.deep.equal(res); + expect(PIXI.utils.removeItems).to.be.a('function'); }); }); diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/test/core/Application.js b/test/core/Application.js index aa726b0..00eb286 100644 --- a/test/core/Application.js +++ b/test/core/Application.js @@ -17,4 +17,21 @@ done(); }); }); + + it('should remove canvas when destroyed', function (done) + { + const app = new PIXI.Application(); + const view = app.view; + + expect(view).to.be.instanceof(HTMLCanvasElement); + document.body.appendChild(view); + + app.ticker.addOnce(() => + { + expect(document.body.contains(view)).to.be.true; + app.destroy(true); + expect(document.body.contains(view)).to.be.false; + done(); + }); + }); }); diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 11e47a0..505df4c 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -140,6 +140,28 @@ expect(graphics.containsPoint(point)).to.be.false; }); + + it('should return false with hole', function () + { + const point1 = new PIXI.Point(1, 1); + const point2 = new PIXI.Point(5, 5); + const graphics = new PIXI.Graphics(); + + graphics.beginFill(0) + .moveTo(0, 0) + .lineTo(10, 0) + .lineTo(10, 10) + .lineTo(0, 10) + // draw hole + .moveTo(2, 2) + .lineTo(8, 2) + .lineTo(8, 8) + .lineTo(2, 8) + .addHole(); + + expect(graphics.containsPoint(point1)).to.be.true; + expect(graphics.containsPoint(point2)).to.be.false; + }); }); describe('arc', function () diff --git a/test/core/Texture.js b/test/core/Texture.js index 6e1109c..0a7919d 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -70,26 +70,6 @@ expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); }); - it('should be added to the texture cache correctly using legacy addTextureToCache, ' - + 'and should remove also remove the base texture from its cache with removeTextureFromCache', function () - { - cleanCache(); - - const texture = new PIXI.Texture(new PIXI.BaseTexture()); - - PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); - PIXI.Texture.addTextureToCache(texture, NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); - expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); - PIXI.Texture.removeTextureFromCache(NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); - expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); - }); - it('should remove Texture from entire cache using removeFromCache (by Texture instance)', function () { cleanCache(); diff --git a/test/core/util.js b/test/core/util.js index 239aaeb..68c7e6d 100755 --- a/test/core/util.js +++ b/test/core/util.js @@ -280,39 +280,9 @@ describe('.removeItems', function () { - var arr; - - beforeEach(() => + it('should exist', function () { - arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - }); - - it('should return if the start index is greater than or equal to the length of the array', function () - { - PIXI.utils.removeItems(arr, arr.length + 1, 5); - expect(arr.length).to.be.equal(10); - }); - - it('should return if the remove count is 0', function () - { - PIXI.utils.removeItems(arr, 2, 0); - expect(arr.length).to.be.equal(10); - }); - - it('should remove the number of elements specified from the array, starting from the start index', function () - { - const res = [1, 2, 3, 8, 9, 10]; - - PIXI.utils.removeItems(arr, 3, 4); - expect(arr).to.be.deep.equal(res); - }); - - it('should remove other elements if delete count is > than the number of elements after start index', function () - { - const res = [1, 2, 3, 4, 5, 6, 7]; - - PIXI.utils.removeItems(arr, 7, 10); - expect(arr).to.be.deep.equal(res); + expect(PIXI.utils.removeItems).to.be.a('function'); }); }); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index de81597..3f84d6a 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -128,6 +128,95 @@ }); }); + describe('touch vs pointer', function () + { + it('should call touchstart and pointerdown when touch event and pointer supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should not call touchstart or pointerdown when pointer event and touch supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.not.have.been.called; + expect(pointerSpy).to.not.have.been.called; + }); + + it('should call touchstart and pointerdown when touch event and pointer not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, false); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should call touchstart and pointerdown when pointer event and touch not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + pointer.interaction.supportsTouchEvents = false; + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + }); + describe('add/remove events', function () { let stub; @@ -283,7 +372,7 @@ expect(element.removeEventListener).to.have.been.calledWith('mouseover'); }); - it('should add and remove touch events to element', function () + it('should add and remove touch events to element without pointer events', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; @@ -306,6 +395,30 @@ expect(element.removeEventListener).to.have.been.calledWith('touchend'); expect(element.removeEventListener).to.have.been.calledWith('touchmove'); }); + + it('should add and remove touch events to element with pointer events', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); }); describe('onClick', function () @@ -1081,7 +1194,7 @@ expect(pointer.renderer.view.style.display).to.equal('none'); }); - it('should not change cursor style if no cursor style provided', function () + it('should not change cursor style if null cursor style provided', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); @@ -1101,6 +1214,22 @@ pointer.mousemove(60, 60); expect(pointer.renderer.view.style.cursor).to.equal(''); }); + + it('should use cursor property as css if no style entry', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.cursor = 'text'; + + pointer.mousemove(10, 10); + expect(pointer.renderer.view.style.cursor).to.equal('text'); + }); }); describe('recursive hitTesting', function () @@ -1298,4 +1427,91 @@ expect(hit).to.equal(behind); }); }); + + describe('InteractionData properties', function () + { + it('isPrimary should be set for first touch only', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.touchstart(10, 10, 1); + expect(pointer.interaction.activeInteractionData[1]).to.exist; + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should be primary on touch start').to.be.true; + pointer.touchstart(13, 9, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should not be primary').to.be.false; + pointer.touchmove(10, 20, 1); + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should still be primary after move').to.be.true; + pointer.touchend(10, 10, 1); + pointer.touchmove(13, 29, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should still not be primary after first is done').to.be.false; + }); + }); + + describe('mouse events from pens', function () + { + it('should call mousedown handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousedown', eventSpy); + + pointer.pendown(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mousemove handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousemove', eventSpy); + + pointer.penmove(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mouseup handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mouseup', eventSpy); + + pointer.penup(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + }); }); diff --git a/.travis.yml b/.travis.yml index 14de334..c4135cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & script: - - npm run build - - xvfb-maybe npm run coverage + - xvfb-maybe npm test - npm run docs deploy: diff --git a/README.md b/README.md index 3b85f85..e1aa27f 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,9 @@ It's easy to get started with Pixi.js! Simply download a [prebuilt build](https://github.com/pixijs/pixi.js/wiki/FAQs#where-can-i-get-a-build)! -Alternatively, Pixi.js can be installed with [Bower](https://bower.io/#getting-started), [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. +Alternatively, Pixi.js can be installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or simply using a content delivery network (CDN) URL to embed Pixi.js directly on your HTML page. -#### Bower Install - -``` -$> bower install pixi.js -``` +_Note: After v4.5.0, support for the [Bower](https://bower.io) package manager has been dropped. Please see the [release notes](https://github.com/pixijs/pixi.js/releases/tag/v4.5.0) for more information._ #### NPM Install @@ -62,10 +58,10 @@ #### CDN Install (via cdnjs) ```html - + ``` -_Note: `4.4.3` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ +_Note: `4.5.1` can be replaced by any [released](https://github.com/pixijs/pixi.js/releases) version._ ### Demos ### diff --git a/package.json b/package.json index df623d5..ce17a4f 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", "watch:lint": "watch \"eslint scripts src test || exit 0\" src", - "test": "floss --path test/index.js", - "test:debug": "npm test -- --debug", + "test": "npm run lint && npm run dist && npm run coverage", + "unit-test": "floss --path test/index.js", + "unit-test:debug": "npm run unit-test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", "renders": "electron scripts/renders", "precoverage": "rimraf coverage", - "coverage": "npm test -- -c dist/pixi.js -s -h", + "coverage": "npm run unit-test -- -c dist/pixi.js -s -h", "lint": "eslint scripts src test --max-warnings 0", "lintfix": "npm run lint --fix", "prebuild": "npm run lint", @@ -43,7 +44,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "postversion": "npm run clean && npm run build && npm run lib && npm test", + "postversion": "npm run clean && npm run build && npm run lib && npm run unit-test", "postpublish": "git push && git push --tags && npm run release", "release": "node scripts/release" }, @@ -62,6 +63,7 @@ "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", "pixi-gl-core": "^1.0.3", + "remove-array-items": "^1.0.0", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 9def67f..ac5399d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -865,6 +865,19 @@ { if (data.shape.contains(tempPoint.x, tempPoint.y)) { + if (data.holes) + { + for (let i = 0; i < data.holes.length; i++) + { + const hole = data.holes[i]; + + if (hole.contains(tempPoint.x, tempPoint.y)) + { + return false; + } + } + } + return true; } } @@ -994,10 +1007,10 @@ const padding = this.boundsPadding; this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + (padding * 2); + this._localBounds.maxX = maxX + padding; this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + (padding * 2); + this._localBounds.maxY = maxY + padding; } /** diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index d9a1777..dd53b06 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -223,6 +223,9 @@ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this._activeShader = null; + this._activeVao = null; + this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); @@ -674,8 +677,8 @@ */ handleContextRestored() { - this._initContext(); this.textureManager.removeAll(); + this._initContext(); } /** diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index d2ab0c4..15820c0 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -126,7 +126,7 @@ if (sprite.tint !== 0xFFFFFF) { - if (sprite.cachedTint !== sprite.tint) + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) { sprite.cachedTint = sprite.tint; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js index fd9de5d..af018d4 100644 --- a/src/core/sprites/canvas/CanvasTinter.js +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -26,17 +26,28 @@ texture.tintCache = texture.tintCache || {}; - if (texture.tintCache[stringColor]) + const cachedTexture = texture.tintCache[stringColor]; + + let canvas; + + if (cachedTexture) { - return texture.tintCache[stringColor]; + if (cachedTexture.tintId === texture._updateID) + { + return texture.tintCache[stringColor]; + } + + canvas = texture.tintCache[stringColor]; + } + else + { + canvas = CanvasTinter.canvas || document.createElement('canvas'); } - // clone texture.. - const canvas = CanvasTinter.canvas || document.createElement('canvas'); - - // CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); CanvasTinter.tintMethod(texture, color, canvas); + canvas.tintId = texture._updateID; + if (CanvasTinter.convertTintToImage) { // is this better? diff --git a/src/core/text/Text.js b/src/core/text/Text.js index a96682f..d81216d 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -134,6 +134,7 @@ this._font = this._style.toFontString(); + const context = this.context; const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); const width = measured.width; const height = measured.height; @@ -146,32 +147,32 @@ this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); - this.context.scale(this.resolution, this.resolution); + context.scale(this.resolution, this.resolution); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; + context.font = this._font; + context.strokeStyle = style.stroke; + context.lineWidth = style.strokeThickness; + context.textBaseline = style.textBaseline; + context.lineJoin = style.lineJoin; + context.miterLimit = style.miterLimit; let linePositionX; let linePositionY; if (style.dropShadow) { - this.context.shadowBlur = style.dropShadowBlur; - this.context.globalAlpha = style.dropShadowAlpha; + context.shadowBlur = style.dropShadowBlur; + context.globalAlpha = style.dropShadowAlpha; if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; + context.shadowColor = style.dropShadowColor; } else { - this.context.fillStyle = style.dropShadowColor; + context.fillStyle = style.dropShadowColor; } const xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; @@ -200,24 +201,24 @@ if (style.stroke && style.strokeThickness) { - this.context.strokeStyle = style.dropShadowColor; + context.strokeStyle = style.dropShadowColor; this.drawLetterSpacing( lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true ); - this.context.strokeStyle = style.stroke; + context.strokeStyle = style.stroke; } } } } // reset the shadow blur and alpha that was set by the drop shadow, for the regular text - this.context.shadowBlur = 0; - this.context.globalAlpha = 1; + context.shadowBlur = 0; + context.globalAlpha = 1; // set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); + context.fillStyle = this._generateFillStyle(style, lines); // draw lines line by line for (let i = 0; i < lines.length; i++) @@ -314,29 +315,32 @@ */ updateTexture() { + const canvas = this.canvas; + if (this._style.trim) { - const trimmed = trimCanvas(this.canvas); + const trimmed = trimCanvas(canvas); - this.canvas.width = trimmed.width; - this.canvas.height = trimmed.height; + canvas.width = trimmed.width; + canvas.height = trimmed.height; this.context.putImageData(trimmed.data, 0, 0); } const texture = this._texture; const style = this._style; const padding = style.trim ? 0 : style.padding; + const baseTexture = texture.baseTexture; - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; + baseTexture.hasLoaded = true; + baseTexture.resolution = this.resolution; - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; + baseTexture.realWidth = canvas.width; + baseTexture.realHeight = canvas.height; + baseTexture.width = canvas.width / this.resolution; + baseTexture.height = canvas.height / this.resolution; + texture.trim.width = texture._frame.width = canvas.width / this.resolution; + texture.trim.height = texture._frame.height = canvas.height / this.resolution; texture.trim.x = -padding; texture.trim.y = -padding; @@ -346,7 +350,7 @@ // call sprite onTextureUpdate to update scale if _width or _height were set this._onTextureUpdate(); - texture.baseTexture.emit('update', texture.baseTexture); + baseTexture.emit('update', baseTexture); this.dirty = false; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 098b383..22e2d98 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -4,6 +4,7 @@ import pluginTarget from './pluginTarget'; import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; +import removeItems from 'remove-array-items'; let nextUid = 0; let saidHello = false; @@ -35,6 +36,15 @@ * @type {Object} */ isMobile, + + /** + * @see {@link https://github.com/mreinstein/remove-array-items} + * + * @memberof PIXI.utils + * @function removeItems + * @type {Object} + */ + removeItems, /** * @see {@link https://github.com/primus/eventemitter3} * @@ -333,36 +343,6 @@ } /** - * Remove a range of items from an array - * - * @memberof PIXI.utils - * @function removeItems - * @param {Array<*>} arr The target array - * @param {number} startIdx The index to begin removing from (inclusive) - * @param {number} removeCount How many items to remove - */ -export function removeItems(arr, startIdx, removeCount) -{ - const length = arr.length; - - if (startIdx >= length || removeCount === 0) - { - return; - } - - removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount); - - const len = length - removeCount; - - for (let i = startIdx; i < len; ++i) - { - arr[i] = arr[i + removeCount]; - } - - arr.length = len; -} - -/** * @todo Describe property usage * * @memberof PIXI.utils diff --git a/src/extras/AnimatedSprite.js b/src/extras/AnimatedSprite.js index cf0ff29..a24f07c 100644 --- a/src/extras/AnimatedSprite.js +++ b/src/extras/AnimatedSprite.js @@ -354,6 +354,8 @@ this._durations.push(value[i].time); } } + this.gotoAndStop(0); + this.updateTexture(); } /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0a0e150..1340fcc 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import settings from '../core/settings'; /** * A BitmapText object will create a line or multiple lines of text using bitmap font. To @@ -469,10 +470,11 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; + const res = texture.baseTexture.resolution || settings.RESOLUTION; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); - data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10); + data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; // parse letters @@ -484,16 +486,16 @@ const charCode = parseInt(letter.getAttribute('id'), 10); const textureRect = new core.Rectangle( - parseInt(letter.getAttribute('x'), 10) + texture.frame.x, - parseInt(letter.getAttribute('y'), 10) + texture.frame.y, - parseInt(letter.getAttribute('width'), 10), - parseInt(letter.getAttribute('height'), 10) + (parseInt(letter.getAttribute('x'), 10) / res) + (texture.frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (texture.frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res ); data.chars[charCode] = { - xOffset: parseInt(letter.getAttribute('xoffset'), 10), - yOffset: parseInt(letter.getAttribute('yoffset'), 10), - xAdvance: parseInt(letter.getAttribute('xadvance'), 10), + xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, + yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, + xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, texture: new core.Texture(texture.baseTexture, textureRect), @@ -506,9 +508,9 @@ for (let i = 0; i < kernings.length; i++) { const kerning = kernings[i]; - const first = parseInt(kerning.getAttribute('first'), 10); - const second = parseInt(kerning.getAttribute('second'), 10); - const amount = parseInt(kerning.getAttribute('amount'), 10); + const first = parseInt(kerning.getAttribute('first'), 10) / res; + const second = parseInt(kerning.getAttribute('second'), 10) / res; + const amount = parseInt(kerning.getAttribute('amount'), 10) / res; if (data.chars[second]) { diff --git a/src/extras/index.js b/src/extras/index.js index a34d7ec..5b98cb1 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,6 +3,7 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; +export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..8fb9416 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -909,6 +911,12 @@ break; } } + else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) + { + // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry + // for the mode, then assume that the dev wants it to be CSS for the cursor. + this.interactionDOMElement.style.cursor = mode; + } } /** @@ -1116,6 +1124,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1161,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1181,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1192,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1247,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1269,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1284,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1293,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1308,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1324,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1410,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1444,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1465,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1492,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1515,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1537,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1613,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1630,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1666,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1729,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1740,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1766,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/loaders/index.js b/src/loaders/index.js index daa1fc4..6c3242a 100644 --- a/src/loaders/index.js +++ b/src/loaders/index.js @@ -69,12 +69,12 @@ // Override the destroy function // making sure to destroy the current Loader AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy() +AppPrototype.destroy = function destroy(removeView) { if (this._loader) { this._loader.destroy(); this._loader = null; } - this._parentDestroy(); + this._parentDestroy(removeView); }; diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 63f7791..6d98098 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -1,4 +1,5 @@ import * as core from '../core'; +import { hex2rgb } from '../core/utils'; /** * The ParticleContainer class is a really fast version of the Container built solely for speed, @@ -121,6 +122,18 @@ this.baseTexture = null; this.setProperties(properties); + + /** + * The tint applied to the container. + * This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @private + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = []; + this.tint = 0xFFFFFF; } /** @@ -153,6 +166,24 @@ } /** + * The tint applied to the container. This is a hex value. + * A value of 0xFFFFFF will remove any tint effect. + ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. + * @member {number} + * @default 0xFFFFFF + */ + get tint() + { + return this._tint; + } + + set tint(value) // eslint-disable-line require-jsdoc + { + this._tint = value; + hex2rgb(value, this._tintRGB); + } + + /** * Renders the container using the WebGL renderer * * @private diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 1b4323b..16e0c9c 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -153,6 +153,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uAlpha = container.worldAlpha; + this.shader.uniforms.tint = container._tintRGB; // make sure the texture is bound.. const baseTexture = children[0]._texture.baseTexture; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 8c8b056..ebd80d9 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -49,9 +49,10 @@ 'uniform sampler2D uSampler;', 'uniform float uAlpha;', + 'uniform vec3 tint;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}', diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index e45c4dc..3ca0774 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -434,12 +434,9 @@ { if (item instanceof core.TextStyle) { - const font = core.Text.getFontStyle(item); + const font = item.toFontString(); - if (!core.Text.fontPropertiesCache[font]) - { - core.Text.calculateFontProperties(font); - } + core.TextMetrics.measureFont(font); return true; } diff --git a/test/core/Application.js b/test/core/Application.js index aa726b0..00eb286 100644 --- a/test/core/Application.js +++ b/test/core/Application.js @@ -17,4 +17,21 @@ done(); }); }); + + it('should remove canvas when destroyed', function (done) + { + const app = new PIXI.Application(); + const view = app.view; + + expect(view).to.be.instanceof(HTMLCanvasElement); + document.body.appendChild(view); + + app.ticker.addOnce(() => + { + expect(document.body.contains(view)).to.be.true; + app.destroy(true); + expect(document.body.contains(view)).to.be.false; + done(); + }); + }); }); diff --git a/test/core/Graphics.js b/test/core/Graphics.js index 11e47a0..505df4c 100644 --- a/test/core/Graphics.js +++ b/test/core/Graphics.js @@ -140,6 +140,28 @@ expect(graphics.containsPoint(point)).to.be.false; }); + + it('should return false with hole', function () + { + const point1 = new PIXI.Point(1, 1); + const point2 = new PIXI.Point(5, 5); + const graphics = new PIXI.Graphics(); + + graphics.beginFill(0) + .moveTo(0, 0) + .lineTo(10, 0) + .lineTo(10, 10) + .lineTo(0, 10) + // draw hole + .moveTo(2, 2) + .lineTo(8, 2) + .lineTo(8, 8) + .lineTo(2, 8) + .addHole(); + + expect(graphics.containsPoint(point1)).to.be.true; + expect(graphics.containsPoint(point2)).to.be.false; + }); }); describe('arc', function () diff --git a/test/core/Texture.js b/test/core/Texture.js index 6e1109c..0a7919d 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -70,26 +70,6 @@ expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); }); - it('should be added to the texture cache correctly using legacy addTextureToCache, ' - + 'and should remove also remove the base texture from its cache with removeTextureFromCache', function () - { - cleanCache(); - - const texture = new PIXI.Texture(new PIXI.BaseTexture()); - - PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); - PIXI.Texture.addTextureToCache(texture, NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); - expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); - PIXI.Texture.removeTextureFromCache(NAME); - expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); - expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); - expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); - }); - it('should remove Texture from entire cache using removeFromCache (by Texture instance)', function () { cleanCache(); diff --git a/test/core/util.js b/test/core/util.js index 239aaeb..68c7e6d 100755 --- a/test/core/util.js +++ b/test/core/util.js @@ -280,39 +280,9 @@ describe('.removeItems', function () { - var arr; - - beforeEach(() => + it('should exist', function () { - arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - }); - - it('should return if the start index is greater than or equal to the length of the array', function () - { - PIXI.utils.removeItems(arr, arr.length + 1, 5); - expect(arr.length).to.be.equal(10); - }); - - it('should return if the remove count is 0', function () - { - PIXI.utils.removeItems(arr, 2, 0); - expect(arr.length).to.be.equal(10); - }); - - it('should remove the number of elements specified from the array, starting from the start index', function () - { - const res = [1, 2, 3, 8, 9, 10]; - - PIXI.utils.removeItems(arr, 3, 4); - expect(arr).to.be.deep.equal(res); - }); - - it('should remove other elements if delete count is > than the number of elements after start index', function () - { - const res = [1, 2, 3, 4, 5, 6, 7]; - - PIXI.utils.removeItems(arr, 7, 10); - expect(arr).to.be.deep.equal(res); + expect(PIXI.utils.removeItems).to.be.a('function'); }); }); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index de81597..3f84d6a 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -128,6 +128,95 @@ }); }); + describe('touch vs pointer', function () + { + it('should call touchstart and pointerdown when touch event and pointer supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should not call touchstart or pointerdown when pointer event and touch supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.not.have.been.called; + expect(pointerSpy).to.not.have.been.called; + }); + + it('should call touchstart and pointerdown when touch event and pointer not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, false); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should call touchstart and pointerdown when pointer event and touch not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + pointer.interaction.supportsTouchEvents = false; + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + }); + describe('add/remove events', function () { let stub; @@ -283,7 +372,7 @@ expect(element.removeEventListener).to.have.been.calledWith('mouseover'); }); - it('should add and remove touch events to element', function () + it('should add and remove touch events to element without pointer events', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; @@ -306,6 +395,30 @@ expect(element.removeEventListener).to.have.been.calledWith('touchend'); expect(element.removeEventListener).to.have.been.calledWith('touchmove'); }); + + it('should add and remove touch events to element with pointer events', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); }); describe('onClick', function () @@ -1081,7 +1194,7 @@ expect(pointer.renderer.view.style.display).to.equal('none'); }); - it('should not change cursor style if no cursor style provided', function () + it('should not change cursor style if null cursor style provided', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); @@ -1101,6 +1214,22 @@ pointer.mousemove(60, 60); expect(pointer.renderer.view.style.cursor).to.equal(''); }); + + it('should use cursor property as css if no style entry', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.cursor = 'text'; + + pointer.mousemove(10, 10); + expect(pointer.renderer.view.style.cursor).to.equal('text'); + }); }); describe('recursive hitTesting', function () @@ -1298,4 +1427,91 @@ expect(hit).to.equal(behind); }); }); + + describe('InteractionData properties', function () + { + it('isPrimary should be set for first touch only', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.touchstart(10, 10, 1); + expect(pointer.interaction.activeInteractionData[1]).to.exist; + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should be primary on touch start').to.be.true; + pointer.touchstart(13, 9, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should not be primary').to.be.false; + pointer.touchmove(10, 20, 1); + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should still be primary after move').to.be.true; + pointer.touchend(10, 10, 1); + pointer.touchmove(13, 29, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should still not be primary after first is done').to.be.false; + }); + }); + + describe('mouse events from pens', function () + { + it('should call mousedown handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousedown', eventSpy); + + pointer.pendown(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mousemove handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousemove', eventSpy); + + pointer.penmove(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mouseup handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mouseup', eventSpy); + + pointer.penup(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + }); }); diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index 7403d90..80eb76a 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -30,6 +30,7 @@ this.createdPointerEvent = true; } + this.activeTouches = []; this.stage = stage; this.renderer = new PIXI.CanvasRenderer(width || 100, height || 100); this.renderer.sayHello = () => { /* empty */ }; @@ -73,35 +74,110 @@ } /** + * [createEvent description] + * @param {string} eventType `type` of event + * @param {number} x pointer x position + * @param {number} y pointer y position + * @param {number} [identifier] pointer id for touch events + * @param {boolean} [asPointer] If it should be a PointerEvent from a mouse or touch + * @param {boolean} [onCanvas=true] If the event should be on the canvas (as opposed to a different element) + * @return {Event} Generated MouseEvent, TouchEvent, or PointerEvent + */ + createEvent(eventType, x, y, identifier, asPointer, onCanvas = true) + { + let event; + + if (eventType.startsWith('mouse')) + { + if (asPointer) + { + event = new PointerEvent(eventType.replace('mouse', 'pointer'), { + pointerType: 'mouse', + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + } + else + { + event = new MouseEvent(eventType, { + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + } + if (onCanvas) + { + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + } + else if (eventType.startsWith('touch')) + { + if (asPointer) + { + eventType = eventType.replace('touch', 'pointer').replace('start', 'down').replace('end', 'up'); + event = new PointerEvent(eventType, { + pointerType: 'touch', + pointerId: identifier || 0, + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + else + { + const touch = new Touch({ identifier: identifier || 0, target: this.renderer.view }); + + if (eventType.endsWith('start')) + { + this.activeTouches.push(touch); + } + else if (eventType.endsWith('end') || eventType.endsWith('leave')) + { + for (let i = 0; i < this.activeTouches.length; ++i) + { + if (this.activeTouches[i].identifier === touch.identifier) + { + this.activeTouches.splice(i, 1); + break; + } + } + } + event = new TouchEvent(eventType, { + preventDefault: sinon.stub(), + changedTouches: [touch], + touches: this.activeTouches, + }); + + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + } + else + { + event = new PointerEvent(eventType, { + pointerType: 'pen', + pointerId: identifier || 0, + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + + this.setPosition(x, y); + this.render(); + + return event; + } + + /** * @param {number} x - pointer x position * @param {number} y - pointer y position * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ mousemove(x, y, asPointer) { - let mouseEvent; - - if (asPointer) - { - mouseEvent = new PointerEvent('pointermove', { - pointerType: 'mouse', - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); - } - else - { - mouseEvent = new MouseEvent('mousemove', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); - } - - Object.defineProperty(mouseEvent, 'target', { value: this.renderer.view }); - this.setPosition(x, y); - this.render(); // mouseOverRenderer state should be correct, so mouse position to view rect const rect = new PIXI.Rectangle(0, 0, this.renderer.width, this.renderer.height); @@ -109,74 +185,102 @@ { if (!this.interaction.mouseOverRenderer) { - this.interaction.onPointerOver(new MouseEvent('mouseover', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - })); + this.interaction.onPointerOver(this.createEvent('mouseover', x, y, null, asPointer)); } - this.interaction.onPointerMove(mouseEvent); + this.interaction.onPointerMove(this.createEvent('mousemove', x, y, null, asPointer)); } else { - this.interaction.onPointerOut(new MouseEvent('mouseout', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - })); + this.interaction.onPointerOut(this.createEvent('mouseout', x, y, null, asPointer)); } } /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ - click(x, y) + click(x, y, asPointer) { - this.mousedown(x, y); - this.mouseup(x, y); + this.mousedown(x, y, asPointer); + this.mouseup(x, y, asPointer); } /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ - mousedown(x, y) + mousedown(x, y, asPointer) { - const mouseEvent = new MouseEvent('mousedown', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); - - Object.defineProperty(mouseEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerDown(mouseEvent); + this.interaction.onPointerDown(this.createEvent('mousedown', x, y, null, asPointer)); } /** * @param {number} x - pointer x position * @param {number} y - pointer y position * @param {boolean} [onCanvas=true] - if the event happend on the Canvas element or not + * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ - mouseup(x, y, onCanvas = true) + mouseup(x, y, onCanvas = true, asPointer = false) { - const mouseEvent = new MouseEvent('mouseup', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); + this.interaction.onPointerUp(this.createEvent('mouseup', x, y, null, asPointer, onCanvas)); + } - if (onCanvas) - { - Object.defineProperty(mouseEvent, 'target', { value: this.renderer.view }); - } + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + tap(x, y, identifier, asPointer) + { + this.touchstart(x, y, identifier, asPointer); + this.touchend(x, y, identifier, asPointer); + } - this.setPosition(x, y); - this.render(); - this.interaction.onPointerUp(mouseEvent); + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchstart(x, y, identifier, asPointer) + { + this.interaction.onPointerDown(this.createEvent('touchstart', x, y, identifier, asPointer)); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchmove(x, y, identifier, asPointer) + { + this.interaction.onPointerMove(this.createEvent('touchmove', x, y, identifier, asPointer)); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchend(x, y, identifier, asPointer) + { + this.interaction.onPointerUp(this.createEvent('touchend', x, y, identifier, asPointer)); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchleave(x, y, identifier, asPointer) + { + this.interaction.onPointerOut(this.createEvent('touchleave', x, y, identifier, asPointer)); } /** @@ -184,10 +288,9 @@ * @param {number} y - pointer y position * @param {number} [identifier] - pointer id */ - tap(x, y, identifier) + pendown(x, y, identifier) { - this.touchstart(x, y, identifier); - this.touchend(x, y, identifier); + this.interaction.onPointerDown(this.createEvent('pointerdown', x, y, identifier, true)); } /** @@ -195,20 +298,9 @@ * @param {number} y - pointer y position * @param {number} [identifier] - pointer id */ - touchstart(x, y, identifier) + penmove(x, y, identifier) { - const touchEvent = new TouchEvent('touchstart', { - preventDefault: sinon.stub(), - changedTouches: [ - new Touch({ identifier: identifier || 0, target: this.renderer.view }), - ], - }); - - Object.defineProperty(touchEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerDown(touchEvent); + this.interaction.onPointerMove(this.createEvent('pointermove', x, y, identifier, true)); } /** @@ -216,41 +308,9 @@ * @param {number} y - pointer y position * @param {number} [identifier] - pointer id */ - touchend(x, y, identifier) + penup(x, y, identifier) { - const touchEvent = new TouchEvent('touchend', { - preventDefault: sinon.stub(), - changedTouches: [ - new Touch({ identifier: identifier || 0, target: this.renderer.view }), - ], - }); - - Object.defineProperty(touchEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerUp(touchEvent); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - * @param {number} [identifier] - pointer id - */ - touchleave(x, y, identifier) - { - const touchEvent = new TouchEvent('touchleave', { - preventDefault: sinon.stub(), - changedTouches: [ - new Touch({ identifier: identifier || 0, target: this.renderer.view }), - ], - }); - - Object.defineProperty(touchEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerOut(touchEvent); + this.interaction.onPointerUp(this.createEvent('pointerup', x, y, identifier, true)); } }